/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Dongle BUS interface
 * USB Linux Implementation
 *
 * Copyright (C) 1999-2016, Broadcom Corporation
 * 
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 * 
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions of
 * the license of that module.  An independent module is a module which is not
 * derived from this software.  The special exception does not apply to any
 * modifications of the software.
 * 
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dbus_usb_linux.c 564663 2015-06-18 02:34:42Z $
 */

/**
 * @file @brief
 * This file contains DBUS code that is USB *and* OS (Linux) specific. DBUS is a Broadcom
 * proprietary host specific abstraction layer.
 */

#include <typedefs.h>
#include <osl.h>

/**
 * DBUS_LINUX_RXDPC is created for router platform performance tuning. A separate thread is created
 * to handle USB RX and avoid the call chain getting too long and enhance cache hit rate.
 *
 * DBUS_LINUX_RXDPC setting is in wlconfig file.
 */

/*
 * If DBUS_LINUX_RXDPC is off, spin_lock_bh() for CTFPOOL in
 * linux_osl.c has to be changed to spin_lock_irqsave() because
 * PKTGET/PKTFREE are no longer in bottom half.
 *
 * Right now we have another queue rpcq in wl_linux.c. Maybe we
 * can eliminate that one to reduce the overhead.
 *
 * Enabling 2nd EP and DBUS_LINUX_RXDPC causing traffic from
 * both EP's to be queued in the same rx queue. If we want
 * RXDPC to work with 2nd EP. The EP for RPC call return
 * should bypass the dpc and go directly up.
 */

/* #define DBUS_LINUX_RXDPC */

/* Dbus histogram for ntxq, nrxq, dpc parameter tuning */
/* #define DBUS_LINUX_HIST */

#include <usbrdl.h>
#include <bcmendian.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/random.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <asm/unaligned.h>
#include <dbus.h>
#include <bcmutils.h>
#include <bcmdevs.h>
#include <linux/usb.h>
#include <usbrdl.h>
#include <linux/firmware.h>
#include <dngl_stats.h>
#include <dhd.h>

#if defined(USBOS_THREAD) || defined(USBOS_TX_THREAD)

/**
 * The usb-thread is designed to provide currency on multiprocessors and SMP linux kernels. On the
 * dual cores platform, the WLAN driver, without threads, executed only on CPU0. The driver consumed
 * almost of 100% on CPU0, while CPU1 remained idle. The behavior was observed on Broadcom's STB.
 *
 * The WLAN driver consumed most of CPU0 and not CPU1 because tasklets/queues, software irq, and
 * hardware irq are executing from CPU0, only. CPU0 became the system's bottle-neck. TPUT is lower
 * and system's responsiveness is slower.
 *
 * To improve system responsiveness and TPUT usb-thread was implemented. The system's threads could
 * be scheduled to run on any core. One core could be processing data in the usb-layer and the other
 * core could be processing data in the wl-layer.
 *
 * For further info see [WlThreadAndUsbThread] Twiki.
 */

#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/hardirq.h>
#include <linux/list.h>
#include <linux_osl.h>
#endif /* USBOS_THREAD || USBOS_TX_THREAD */



#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
#define KERNEL26
#endif

/**
 * Starting with the 3.10 kernel release, dynamic PM support for USB is present whenever
 * the kernel was built with CONFIG_PM_RUNTIME enabled. The CONFIG_USB_SUSPEND option has
 * been eliminated.
 */
#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21)) && defined(CONFIG_USB_SUSPEND)) \
	|| ((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) && defined(CONFIG_PM_RUNTIME))
/* For USB power management support, see Linux kernel: Documentation/usb/power-management.txt */
#define USB_SUSPEND_AVAILABLE
#endif

/* Define alternate fw/nvram paths used in Android */
#ifdef OEM_ANDROID
#define CONFIG_ANDROID_BCMDHD_FW_PATH "broadcom/dhd/firmware/fw.bin.trx"
#define CONFIG_ANDROID_BCMDHD_NVRAM_PATH "broadcom/dhd/nvrams/nvm.txt"
#endif /* OEM_ANDROID */

static inline int usb_submit_urb_linux(struct urb *urb)
{

#ifdef BCM_MAX_URB_LEN
	if (urb && (urb->transfer_buffer_length > BCM_MAX_URB_LEN)) {
		DBUSERR(("URB transfer length=%d exceeded %d ra=%p\n", urb->transfer_buffer_length,
		BCM_MAX_URB_LEN, __builtin_return_address(0)));
		return DBUS_ERR;
	}
#endif

#ifdef KERNEL26
	return usb_submit_urb(urb, GFP_ATOMIC);
#else
	return usb_submit_urb(urb);
#endif

}

#define USB_SUBMIT_URB(urb) usb_submit_urb_linux(urb)

#ifdef KERNEL26

#define USB_ALLOC_URB()				usb_alloc_urb(0, GFP_ATOMIC)
#define USB_UNLINK_URB(urb)			(usb_kill_urb(urb))
#define USB_FREE_URB(urb)			(usb_free_urb(urb))
#define USB_REGISTER()				usb_register(&dbus_usbdev)
#define USB_DEREGISTER()			usb_deregister(&dbus_usbdev)

#ifdef USB_SUSPEND_AVAILABLE

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
#define USB_AUTOPM_SET_INTERFACE(intf)		usb_autopm_set_interface(intf)
#else
#define USB_ENABLE_AUTOSUSPEND(udev)		usb_enable_autosuspend(udev)
#define USB_DISABLE_AUTOSUSPEND(udev)       usb_disable_autosuspend(udev)
#endif  /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))  */

#define USB_AUTOPM_GET_INTERFACE(intf)		usb_autopm_get_interface(intf)
#define USB_AUTOPM_PUT_INTERFACE(intf)		usb_autopm_put_interface(intf)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	usb_autopm_get_interface_async(intf)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	usb_autopm_put_interface_async(intf)
#define USB_MARK_LAST_BUSY(dev)			usb_mark_last_busy(dev)

#else /* USB_SUSPEND_AVAILABLE */

#define USB_AUTOPM_GET_INTERFACE(intf)		do {} while (0)
#define USB_AUTOPM_PUT_INTERFACE(intf)		do {} while (0)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	do {} while (0)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	do {} while (0)
#define USB_MARK_LAST_BUSY(dev)			do {} while (0)
#endif /* USB_SUSPEND_AVAILABLE */

#define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data, size, timeout) \
	usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
	(data), (size), (timeout))
#define USB_BULK_MSG(dev, pipe, data, len, actual_length, timeout) \
	usb_bulk_msg((dev), (pipe), (data), (len), (actual_length), (timeout))
#define USB_BUFFER_ALLOC(dev, size, mem, dma)	usb_buffer_alloc(dev, size, mem, dma)
#define USB_BUFFER_FREE(dev, size, data, dma)	usb_buffer_free(dev, size, data, dma)

#ifdef WL_URB_ZPKT
#define URB_QUEUE_BULK   URB_ZERO_PACKET
#else
#define URB_QUEUE_BULK   0
#endif /* WL_URB_ZPKT */

#define CALLBACK_ARGS		struct urb *urb, struct pt_regs *regs
#define CALLBACK_ARGS_DATA	urb, regs
#define CONFIGDESC(usb)		(&((usb)->actconfig)->desc)
#define IFPTR(usb, idx)		((usb)->actconfig->interface[idx])
#define IFALTS(usb, idx)	(IFPTR((usb), (idx))->altsetting[0])
#define IFDESC(usb, idx)	IFALTS((usb), (idx)).desc
#define IFEPDESC(usb, idx, ep)	(IFALTS((usb), (idx)).endpoint[ep]).desc

#else /* KERNEL26 */

#define USB_ALLOC_URB()				usb_alloc_urb(0)
#define USB_UNLINK_URB(urb)			usb_unlink_urb(urb)
#define USB_FREE_URB(urb)			(usb_free_urb(urb))
#define USB_REGISTER()				usb_register(&dbus_usbdev)
#define USB_DEREGISTER()			usb_deregister(&dbus_usbdev)
#define USB_AUTOPM_GET_INTERFACE(intf)		do {} while (0)
#define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	do {} while (0)
#define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	do {} while (0)
#define USB_MARK_LAST_BUSY(dev)			do {} while (0)

#define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data, size, timeout) \
	usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
	(data), (size), (timeout))
#define USB_BUFFER_ALLOC(dev, size, mem, dma)  kmalloc(size, mem)
#define USB_BUFFER_FREE(dev, size, data, dma)  kfree(data)

#ifdef WL_URB_ZPKT
#define URB_QUEUE_BULK   USB_QUEUE_BULK|URB_ZERO_PACKET
#else
#define URB_QUEUE_BULK   0
#endif /*  WL_URB_ZPKT */

#define CALLBACK_ARGS		struct urb *urb
#define CALLBACK_ARGS_DATA	urb
#define CONFIGDESC(usb)		((usb)->actconfig)
#define IFPTR(usb, idx)		(&(usb)->actconfig->interface[idx])
#define IFALTS(usb, idx)	((usb)->actconfig->interface[idx].altsetting[0])
#define IFDESC(usb, idx)	IFALTS((usb), (idx))
#define IFEPDESC(usb, idx, ep)	(IFALTS((usb), (idx)).endpoint[ep])


#endif /* KERNEL26 */

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31))
#define USB_SPEED_SUPER		5
#endif  /* #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)) */

#define CONTROL_IF   0
#define BULK_IF      0

#ifdef BCMUSBDEV_COMPOSITE
#define USB_COMPIF_MAX       4

#define USB_CLASS_WIRELESS	0xe0
#define USB_CLASS_MISC		0xef
#define USB_SUBCLASS_COMMON	0x02
#define USB_PROTO_IAD		0x01
#define USB_PROTO_VENDOR	0xff

#define USB_QUIRK_NO_SET_INTF   0x04 /* device does not support set_interface */
#endif /* BCMUSBDEV_COMPOSITE */

#define USB_SYNC_WAIT_TIMEOUT  300  /* ms */

/* Private data kept in skb */
#define SKB_PRIV(skb, idx)  (&((void **)skb->cb)[idx])
#define SKB_PRIV_URB(skb)   (*(struct urb **)SKB_PRIV(skb, 0))

#ifndef DBUS_USB_RXQUEUE_BATCH_ADD
/* items to add each time within limit */
#define DBUS_USB_RXQUEUE_BATCH_ADD            8
#endif

#ifndef DBUS_USB_RXQUEUE_LOWER_WATERMARK
/* add a new batch req to rx queue when waiting item count reduce to this number */
#define DBUS_USB_RXQUEUE_LOWER_WATERMARK      4
#endif

enum usbos_suspend_state {
	USBOS_SUSPEND_STATE_DEVICE_ACTIVE = 0, /* Device is busy, won't allow suspend */
	USBOS_SUSPEND_STATE_SUSPEND_PENDING,   /* Device is idle, can be suspended */
	                                       /* Wating PM to suspend */
	USBOS_SUSPEND_STATE_SUSPENDED          /* Device suspended */
};

enum usbos_request_state {
	USBOS_REQUEST_STATE_UNSCHEDULED = 0,	/* USB TX request not scheduled */
	USBOS_REQUEST_STATE_SCHEDULED,		/* USB TX request given to TX thread */
	USBOS_REQUEST_STATE_SUBMITTED		/* USB TX request submitted */
};

typedef struct {
	uint32 notification;
	uint32 reserved;
} intr_t;

typedef struct {
	dbus_pub_t *pub;

	void *cbarg;
	dbus_intf_callbacks_t *cbs;

	/* Imported */
	struct usb_device *usb;	/* USB device pointer from OS */
	struct urb *intr_urb; /* URB for interrupt endpoint */
	struct list_head req_rxfreeq;
	struct list_head req_txfreeq;
	struct list_head req_rxpostedq;	/* Posted down to USB driver for RX */
	struct list_head req_txpostedq;	/* Posted down to USB driver for TX */
	spinlock_t rxfree_lock; /* Lock for rx free list */
	spinlock_t txfree_lock; /* Lock for tx free list */
	spinlock_t rxposted_lock; /* Lock for rx posted list */
	spinlock_t txposted_lock; /* Lock for tx posted list */
	uint rx_pipe, tx_pipe, intr_pipe, rx_pipe2; /* Pipe numbers for USB I/O */
	uint rxbuf_len;

	struct list_head req_rxpendingq; /* RXDPC: Pending for dpc to send up */
	spinlock_t rxpending_lock;	/* RXDPC: Lock for rx pending list */
	long dpc_pid;
	struct semaphore dpc_sem;
	struct completion dpc_exited;
	int rxpending;

	struct urb               *ctl_urb;
	int                      ctl_in_pipe, ctl_out_pipe;
	struct usb_ctrlrequest   ctl_write;
	struct usb_ctrlrequest   ctl_read;
	struct semaphore         ctl_lock;     /* Lock for CTRL transfers via tx_thread */
#ifdef USBOS_TX_THREAD
	enum usbos_request_state ctl_state;
#endif /* USBOS_TX_THREAD */

	spinlock_t rxlock;      /* Lock for rxq management */
	spinlock_t txlock;      /* Lock for txq management */

	int intr_size;          /* Size of interrupt message */
	int interval;           /* Interrupt polling interval */
	intr_t intr;            /* Data buffer for interrupt endpoint */

	int maxps;
	atomic_t txposted;
	atomic_t rxposted;
	atomic_t txallocated;
	atomic_t rxallocated;
	bool rxctl_deferrespok;	/* Get a response for setup from dongle */

	wait_queue_head_t wait;
	bool waitdone;
	int sync_urb_status;

	struct urb *blk_urb; /* Used for downloading embedded image */

#ifdef USBOS_THREAD
	spinlock_t              ctrl_lock;
	spinlock_t              usbos_list_lock;
	struct list_head        usbos_list;
	struct list_head        usbos_free_list;
	atomic_t                usbos_list_cnt;
	wait_queue_head_t       usbos_queue_head;
	struct task_struct      *usbos_kt;
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
	spinlock_t              usbos_tx_list_lock;
	struct list_head	usbos_tx_list;
	wait_queue_head_t	usbos_tx_queue_head;
	struct task_struct      *usbos_tx_kt;
#endif /* USBOS_TX_THREAD */

	struct dma_pool *qtd_pool; /* QTD pool for USB optimization only */
	int tx_ep, rx_ep, rx2_ep;  /* EPs for USB optimization */
	struct usb_device *usb_device; /* USB device for optimization */
} usbos_info_t;

typedef struct urb_req {
	void         *pkt;
	int          buf_len;
	struct urb   *urb;
	void         *arg;
	usbos_info_t *usbinfo;
	struct list_head urb_list;
} urb_req_t;

#ifdef USBOS_THREAD
typedef struct usbos_list_entry {
	struct list_head    list;   /* must be first */
	void               *urb_context;
	int                 urb_length;
	int                 urb_status;
} usbos_list_entry_t;

static void* dbus_usbos_thread_init(usbos_info_t *usbos_info);
static void  dbus_usbos_thread_deinit(usbos_info_t *usbos_info);
static void  dbus_usbos_dispatch_schedule(CALLBACK_ARGS);
static int   dbus_usbos_thread_func(void *data);
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
void* dbus_usbos_tx_thread_init(usbos_info_t *usbos_info);
void  dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info);
int   dbus_usbos_tx_thread_func(void *data);
#endif /* USBOS_TX_THREAD */

/* Shared Function prototypes */
bool dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer, int buflen);
int dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms);
bool dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len);
int dbus_write_membytes(usbos_info_t *usbinfo, bool set, uint32 address, uint8 *data, uint size);

/* Local function prototypes */
static void dbus_usbos_send_complete(CALLBACK_ARGS);
static void dbus_usbos_recv_complete(CALLBACK_ARGS);
static int  dbus_usbos_errhandler(void *bus, int err);
static int  dbus_usbos_state_change(void *bus, int state);
static void dbusos_stop(usbos_info_t *usbos_info);

#ifdef KERNEL26
static int dbus_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id);
static void dbus_usbos_disconnect(struct usb_interface *intf);
#if defined(USB_SUSPEND_AVAILABLE)
static int dbus_usbos_resume(struct usb_interface *intf);
static int dbus_usbos_suspend(struct usb_interface *intf, pm_message_t message);
/* at the moment, used for full dongle host driver only */
static int dbus_usbos_reset_resume(struct usb_interface *intf);
#endif /* USB_SUSPEND_AVAILABLE */
#else /* KERNEL26 */
static void *dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum,
	const struct usb_device_id *id);
static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr);
#endif /* KERNEL26 */


/**
 * have to disable missing-field-initializers warning as last element {} triggers it
 * and different versions of kernel have different number of members so it is impossible
 * to specify the initializer. BTW issuing the warning here is bug og GCC as  universal
 * zero {0} specified in C99 standard as correct way of initialization of struct to all zeros
 */
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == \
	4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif

static struct usb_device_id devid_table[] = {
	{ USB_DEVICE(BCM_DNGL_VID, 0x0000) }, /* Configurable via register() */
#if defined(BCM_REQUEST_FW)
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4328) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4322) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4319) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43236) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43143) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43242) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4360) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4350) },
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43569) },
#endif
#ifdef EXTENDED_VID_PID
	EXTENDED_VID_PID,
#endif /* EXTENDED_VID_PID */
	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BDC_PID) }, /* Default BDC */
	{ }
};

#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == \
	4 && __GNUC_MINOR__ >= 6))
#pragma GCC diagnostic pop
#endif

MODULE_DEVICE_TABLE(usb, devid_table);

/** functions called by the Linux kernel USB subsystem */
static struct usb_driver dbus_usbdev = {
	name:           "dbus_usbdev",
	probe:          dbus_usbos_probe,
	disconnect:     dbus_usbos_disconnect,
	id_table:       devid_table,
#if defined(USB_SUSPEND_AVAILABLE)
	suspend:        dbus_usbos_suspend,
	resume:         dbus_usbos_resume,
	reset_resume:	dbus_usbos_reset_resume,
	/* Linux USB core will allow autosuspend for devices bound to this driver */
	supports_autosuspend: 1
#endif /* USB_SUSPEND_AVAILABLE */
};

/**
 * This stores USB info during Linux probe callback since attach() is not called yet at this point
 */
typedef struct {
	void    *usbos_info;
	struct usb_device *usb; /* USB device pointer from OS */
	uint    rx_pipe;   /* Pipe numbers for USB I/O */
	uint    tx_pipe;   /* Pipe numbers for USB I/O */
	uint    intr_pipe; /* Pipe numbers for USB I/O */
	uint    rx_pipe2;  /* Pipe numbers for USB I/O */
	int     intr_size; /* Size of interrupt message */
	int     interval;  /* Interrupt polling interval */
	bool    dldone;
	int     vid;
	int     pid;
	bool    dereged;
	bool    disc_cb_done;
	DEVICE_SPEED    device_speed;
	enum usbos_suspend_state suspend_state;
	struct usb_interface     *intf;
} probe_info_t;

/*
 * USB Linux dbus_intf_t
 */
static void *dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg, dbus_intf_callbacks_t *cbs);
static void dbus_usbos_intf_detach(dbus_pub_t *pub, void *info);
static int  dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb);
static int  dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb);
static int  dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb, uint32 ep_idx);
static int  dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb);
static int  dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len);
static int  dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len);
static int  dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib);
static int  dbus_usbos_intf_up(void *bus);
static int  dbus_usbos_intf_down(void *bus);
static int  dbus_usbos_intf_stop(void *bus);
static int  dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen, uint32 *value);
extern int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size);
int dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data);
static int  dbus_usbos_intf_set_config(void *bus, dbus_config_t *config);
static bool dbus_usbos_intf_recv_needed(void *bus);
static void *dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb, struct exec_parms *args);
static void *dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb, struct exec_parms *args);
#ifdef BCMUSBDEV_COMPOSITE
static int dbus_usbos_intf_wlan(struct usb_device *usb);
#endif /* BCMUSBDEV_COMPOSITE */

/** functions called by dbus_usb.c */
static dbus_intf_t dbus_usbos_intf = {
	.attach = dbus_usbos_intf_attach,
	.detach = dbus_usbos_intf_detach,
	.up = dbus_usbos_intf_up,
	.down = dbus_usbos_intf_down,
	.send_irb = dbus_usbos_intf_send_irb,
	.recv_irb = dbus_usbos_intf_recv_irb,
	.cancel_irb = dbus_usbos_intf_cancel_irb,
	.send_ctl = dbus_usbos_intf_send_ctl,
	.recv_ctl = dbus_usbos_intf_recv_ctl,
	.get_stats = NULL,
	.get_attrib = dbus_usbos_intf_get_attrib,
	.remove = NULL,
	.resume = NULL,
	.suspend = NULL,
	.stop = dbus_usbos_intf_stop,
	.reset = NULL,
	.pktget = NULL,
	.pktfree = NULL,
	.iovar_op = NULL,
	.dump = NULL,
	.set_config = dbus_usbos_intf_set_config,
	.get_config = NULL,
	.device_exists = NULL,
	.dlneeded = NULL,
	.dlstart = NULL,
	.dlrun = NULL,
	.recv_needed = dbus_usbos_intf_recv_needed,
	.exec_rxlock = dbus_usbos_intf_exec_rxlock,
	.exec_txlock = dbus_usbos_intf_exec_txlock,

	.tx_timer_init = NULL,
	.tx_timer_start = NULL,
	.tx_timer_stop = NULL,

	.sched_dpc = NULL,
	.lock = NULL,
	.unlock = NULL,
	.sched_probe_cb = NULL,

	.shutdown = NULL,

	.recv_stop = NULL,
	.recv_resume = NULL,

	.recv_irb_from_ep = dbus_usbos_intf_recv_irb_from_ep,
	.readreg = dbus_usbos_readreg
};

static probe_info_t    g_probe_info;
static probe_cb_t      probe_cb = NULL;
static disconnect_cb_t disconnect_cb = NULL;
static void            *probe_arg = NULL;
static void            *disc_arg = NULL;



static volatile int loopback_rx_cnt, loopback_tx_cnt;
int loopback_size;
bool is_loopback_pkt(void *buf);
int matches_loopback_pkt(void *buf);

/**
 * multiple code paths in this file dequeue a URB request, this function makes sure that it happens
 * in a concurrency save manner. Don't call this from a sleepable process context.
 */
static urb_req_t * BCMFASTPATH
dbus_usbos_qdeq(struct list_head *urbreq_q, spinlock_t *lock)
{
	unsigned long flags;
	urb_req_t *req;

	ASSERT(urbreq_q != NULL);

	spin_lock_irqsave(lock, flags);

	if (list_empty(urbreq_q)) {
		req = NULL;
	} else {
		ASSERT(urbreq_q->next != NULL);
		ASSERT(urbreq_q->next != urbreq_q);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
		req = list_entry(urbreq_q->next, urb_req_t, urb_list);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
		list_del_init(&req->urb_list);
	}

	spin_unlock_irqrestore(lock, flags);

	return req;
}

static void BCMFASTPATH
dbus_usbos_qenq(struct list_head *urbreq_q, urb_req_t *req, spinlock_t *lock)
{
	unsigned long flags;

	spin_lock_irqsave(lock, flags);

	list_add_tail(&req->urb_list, urbreq_q);

	spin_unlock_irqrestore(lock, flags);
}

/**
 * multiple code paths in this file remove a URB request from a list, this function makes sure that
 * it happens in a concurrency save manner. Don't call this from a sleepable process context.
 * Is quite similar to dbus_usbos_qdeq(), I wonder why this function is needed.
 */
static void
dbus_usbos_req_del(urb_req_t *req, spinlock_t *lock)
{
	unsigned long flags;

	spin_lock_irqsave(lock, flags);

	list_del_init(&req->urb_list);

	spin_unlock_irqrestore(lock, flags);
}


/**
 * Driver requires a pool of URBs to operate. This function is called during
 * initialization (attach phase), allocates a number of URBs, and puts them
 * on the free (req_rxfreeq and req_txfreeq) queue
 */
static int
dbus_usbos_urbreqs_alloc(usbos_info_t *usbos_info, uint32 count, bool is_rx)
{
	int i;
	int allocated = 0;
	int err = DBUS_OK;

	for (i = 0; i < count; i++) {
		urb_req_t *req;

		req = MALLOC(usbos_info->pub->osh, sizeof(urb_req_t));
		if (req == NULL) {
			DBUSERR(("%s: MALLOC req failed\n", __FUNCTION__));
			err = DBUS_ERR_NOMEM;
			goto fail;
		}
		bzero(req, sizeof(urb_req_t));

		req->urb = USB_ALLOC_URB();
		if (req->urb == NULL) {
			DBUSERR(("%s: USB_ALLOC_URB req->urb failed\n", __FUNCTION__));
			err = DBUS_ERR_NOMEM;
			goto fail;
		}

		INIT_LIST_HEAD(&req->urb_list);

		if (is_rx) {
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
			/* don't allocate now. Do it on demand */
			req->pkt = NULL;
#else
			/* pre-allocate  buffers never to be released */
			req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
			if (req->pkt == NULL) {
				DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
				err = DBUS_ERR_NOMEM;
				goto fail;
			}
#endif
			req->buf_len = usbos_info->rxbuf_len;
			dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
		} else {
			req->buf_len = 0;
			dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
		}
		allocated++;
		continue;

fail:
		if (req) {
			if (is_rx && req->pkt) {
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
				/* req->pkt is NULL in "NOCOPY" mode */
#else
				MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
#endif
			}
			if (req->urb) {
				USB_FREE_URB(req->urb);
			}
			MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));
		}
		break;
	}

	atomic_add(allocated, is_rx ? &usbos_info->rxallocated : &usbos_info->txallocated);

	if (is_rx) {
		DBUSTRACE(("%s: add %d (total %d) rx buf, each has %d bytes\n", __FUNCTION__,
			allocated, atomic_read(&usbos_info->rxallocated), usbos_info->rxbuf_len));
	} else {
		DBUSTRACE(("%s: add %d (total %d) tx req\n", __FUNCTION__,
			allocated, atomic_read(&usbos_info->txallocated)));
	}

	return err;
} /* dbus_usbos_urbreqs_alloc */

/** Typically called during detach or when attach failed. Don't call until all URBs unlinked */
static int
dbus_usbos_urbreqs_free(usbos_info_t *usbos_info, bool is_rx)
{
	int rtn = 0;
	urb_req_t *req;
	struct list_head *req_q;
	spinlock_t *lock;

	if (is_rx) {
		req_q = &usbos_info->req_rxfreeq;
		lock = &usbos_info->rxfree_lock;
	} else {
		req_q = &usbos_info->req_txfreeq;
		lock = &usbos_info->txfree_lock;
	}
	while ((req = dbus_usbos_qdeq(req_q, lock)) != NULL) {

		if (is_rx) {
			if (req->pkt) {
				/* We do MFREE instead of PKTFREE because the pkt has been
				 * converted to native already
				 */
				MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
				req->pkt = NULL;
				req->buf_len = 0;
			}
		} else {
			/* sending req should not be assigned pkt buffer */
			ASSERT(req->pkt == NULL);
		}

		if (req->urb) {
			USB_FREE_URB(req->urb);
			req->urb = NULL;
		}
		MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));

		rtn++;
	}
	return rtn;
} /* dbus_usbos_urbreqs_free */

/**
 * called by Linux kernel on URB completion. Upper DBUS layer (dbus_usb.c) has to be notified of
 * send completion.
 */
void
dbus_usbos_send_complete(CALLBACK_ARGS)
{
	urb_req_t *req = urb->context;
	dbus_irb_tx_t *txirb = req->arg;
	usbos_info_t *usbos_info = req->usbinfo;
	unsigned long flags;
	int status = DBUS_OK;
	int txposted;

	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

	spin_lock_irqsave(&usbos_info->txlock, flags);

	dbus_usbos_req_del(req, &usbos_info->txposted_lock);
	txposted = atomic_dec_return(&usbos_info->txposted);
	if (unlikely (txposted < 0)) {
		DBUSERR(("%s ERROR: txposted is negative (%d)!!\n", __FUNCTION__, txposted));
	}
	spin_unlock_irqrestore(&usbos_info->txlock, flags);

	if (unlikely (urb->status)) {
		status = DBUS_ERR_TXFAIL;
		DBUSTRACE(("txfail status %d\n", urb->status));
	}

#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
	/* sending req should not be assigned pkt buffer */
	ASSERT(req->pkt == NULL);
#endif
	/*  txirb should always be set, except for ZLP. ZLP is reusing this callback function. */
	if (txirb != NULL) {
		if (txirb->send_buf != NULL) {
			MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
			txirb->send_buf = NULL;
			req->buf_len = 0;
		}
		if (likely (usbos_info->cbarg && usbos_info->cbs)) {
			if (likely (usbos_info->cbs->send_irb_complete != NULL))
			    usbos_info->cbs->send_irb_complete(usbos_info->cbarg, txirb, status);
		}
	}

	dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
} /* dbus_usbos_send_complete */

/**
 * In order to receive USB traffic from the dongle, we need to supply the Linux kernel with a free
 * URB that is going to contain received data.
 */
static int BCMFASTPATH
dbus_usbos_recv_urb_submit(usbos_info_t *usbos_info, dbus_irb_rx_t *rxirb, uint32 ep_idx)
{
	urb_req_t *req;
	int ret = DBUS_OK;
	unsigned long flags;
	void *p;
	uint rx_pipe;
	int rxposted;

	BCM_REFERENCE(rxposted);

	if (!(req = dbus_usbos_qdeq(&usbos_info->req_rxfreeq, &usbos_info->rxfree_lock))) {
		DBUSTRACE(("%s No free URB!\n", __FUNCTION__));
		return DBUS_ERR_RXDROP;
	}

	spin_lock_irqsave(&usbos_info->rxlock, flags);

#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
	req->pkt = rxirb->pkt = PKTGET(usbos_info->pub->osh, req->buf_len, FALSE);
	if (!rxirb->pkt) {
		DBUSERR(("%s: PKTGET failed\n", __FUNCTION__));
		dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
		ret = DBUS_ERR_RXDROP;
		goto fail;
	}
	/* consider the packet "native" so we don't count it as MALLOCED in the osl */
	PKTTONATIVE(usbos_info->pub->osh, req->pkt);
	rxirb->buf = NULL;
	p = PKTDATA(usbos_info->pub->osh, req->pkt);
#else
	if (req->buf_len != usbos_info->rxbuf_len) {
		ASSERT(req->pkt);
		MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
		DBUSTRACE(("%s: replace rx buff: old len %d, new len %d\n", __FUNCTION__,
			req->buf_len, usbos_info->rxbuf_len));
		req->buf_len = 0;
		req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
		if (req->pkt == NULL) {
			DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
			ret = DBUS_ERR_NOMEM;
			goto fail;
		}
		req->buf_len = usbos_info->rxbuf_len;
	}
	rxirb->buf = req->pkt;
	p = rxirb->buf;
#endif /* defined(BCM_RPC_NOCOPY) */
	rxirb->buf_len = req->buf_len;
	req->usbinfo = usbos_info;
	req->arg = rxirb;
	if (ep_idx == 0) {
		rx_pipe = usbos_info->rx_pipe;
	} else {
		rx_pipe = usbos_info->rx_pipe2;
		ASSERT(usbos_info->rx_pipe2);
	}
	/* Prepare the URB */
	usb_fill_bulk_urb(req->urb, usbos_info->usb, rx_pipe,
		p,
		rxirb->buf_len,
		(usb_complete_t)dbus_usbos_recv_complete, req);
		req->urb->transfer_flags |= URB_QUEUE_BULK;

	if ((ret = USB_SUBMIT_URB(req->urb))) {
		DBUSERR(("%s USB_SUBMIT_URB failed. status %d\n", __FUNCTION__, ret));
		dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
		ret = DBUS_ERR_RXFAIL;
		goto fail;
	}
	rxposted = atomic_inc_return(&usbos_info->rxposted);

	dbus_usbos_qenq(&usbos_info->req_rxpostedq, req, &usbos_info->rxposted_lock);
fail:
	spin_unlock_irqrestore(&usbos_info->rxlock, flags);
	return ret;
} /* dbus_usbos_recv_urb_submit */


/**
 * Called by worked thread when a 'receive URB' completed or Linux kernel when it returns a URB to
 * this driver.
 */
static void BCMFASTPATH
dbus_usbos_recv_complete_handle(urb_req_t *req, int len, int status)
{
	dbus_irb_rx_t *rxirb = req->arg;
	usbos_info_t *usbos_info = req->usbinfo;
	unsigned long flags;
	int rxallocated, rxposted;
	int dbus_status = DBUS_OK;
	bool killed = (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1 : 0;

	spin_lock_irqsave(&usbos_info->rxlock, flags);
	dbus_usbos_req_del(req, &usbos_info->rxposted_lock);
	rxposted = atomic_dec_return(&usbos_info->rxposted);
	rxallocated = atomic_read(&usbos_info->rxallocated);
	spin_unlock_irqrestore(&usbos_info->rxlock, flags);

	if ((rxallocated < usbos_info->pub->nrxq) && (!status) &&
		(rxposted == DBUS_USB_RXQUEUE_LOWER_WATERMARK)) {
			DBUSTRACE(("%s: need more rx buf: rxallocated %d rxposted %d!\n",
				__FUNCTION__, rxallocated, rxposted));
			dbus_usbos_urbreqs_alloc(usbos_info,
				MIN(DBUS_USB_RXQUEUE_BATCH_ADD,
				usbos_info->pub->nrxq - rxallocated), TRUE);
	}

	/* Handle errors */
	if (status) {
		/*
		 * Linux 2.4 disconnect: -ENOENT or -EILSEQ for CRC error; rmmod: -ENOENT
		 * Linux 2.6 disconnect: -EPROTO, rmmod: -ESHUTDOWN
		 */
		if ((status == -ENOENT && (!killed))|| status == -ESHUTDOWN) {
			/* NOTE: unlink() can not be called from URB callback().
			 * Do not call dbusos_stop() here.
			 */
			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
			dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
		} else if (status == -EPROTO) {
			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
		} else if (killed && (status == -EHOSTUNREACH || status == -ENOENT)) {
			/* Device is suspended */
		} else {
			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
			dbus_usbos_errhandler(usbos_info, DBUS_ERR_RXFAIL);
		}

		/* On error, don't submit more URBs yet */
		rxirb->buf = NULL;
		rxirb->actual_len = 0;
		dbus_status = DBUS_ERR_RXFAIL;
		goto fail;
	}

	/* Make the skb represent the received urb */
	rxirb->actual_len = len;

	if (rxirb->actual_len < sizeof(uint32)) {
		DBUSTRACE(("small pkt len %d, process as ZLP\n", rxirb->actual_len));
		dbus_status = DBUS_ERR_RXZLP;
	}

fail:
#if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
	/* detach the packet from the queue */
	req->pkt = NULL;
#endif /* BCM_RPC_NOCOPY || BCM_RPC_RXNOCOPY */

	if (usbos_info->cbarg && usbos_info->cbs) {
		if (usbos_info->cbs->recv_irb_complete) {
			usbos_info->cbs->recv_irb_complete(usbos_info->cbarg, rxirb, dbus_status);
		}
	}

	dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);

	/* Mark the interface as busy to reset USB autosuspend timer */
	USB_MARK_LAST_BUSY(usbos_info->usb);
} /* dbus_usbos_recv_complete_handle */

/** called by Linux kernel when it returns a URB to this driver */
static void
dbus_usbos_recv_complete(CALLBACK_ARGS)
{
#ifdef USBOS_THREAD
	dbus_usbos_dispatch_schedule(CALLBACK_ARGS_DATA);
#else /*  !USBOS_THREAD */
	dbus_usbos_recv_complete_handle(urb->context, urb->actual_length, urb->status);
#endif /*  USBOS_THREAD */
}


/**
 * If Linux notifies our driver that a control read or write URB has completed, we should notify
 * the DBUS layer above us (dbus_usb.c in this case).
 */
static void
dbus_usbos_ctl_complete(usbos_info_t *usbos_info, int type, int urbstatus)
{
	int status = DBUS_ERR;

	if (usbos_info == NULL)
		return;

	switch (urbstatus) {
		case 0:
			status = DBUS_OK;
		break;
		case -EINPROGRESS:
		case -ENOENT:
		default:
#ifdef INTR_EP_ENABLE
			DBUSERR(("%s:%d fail status %d bus:%d susp:%d intr:%d ctli:%d ctlo:%d\n",
				__FUNCTION__, type, urbstatus,
				usbos_info->pub->busstate, g_probe_info.suspend_state,
				usbos_info->intr_urb_submitted, usbos_info->ctlin_urb_submitted,
				usbos_info->ctlout_urb_submitted));
#else
			DBUSERR(("%s: failed with status %d\n", __FUNCTION__, urbstatus));
			status = DBUS_ERR;
		break;
#endif /* INTR_EP_ENABLE */
	}

	if (usbos_info->cbarg && usbos_info->cbs) {
		if (usbos_info->cbs->ctl_complete)
			usbos_info->cbs->ctl_complete(usbos_info->cbarg, type, status);
	}
}

/** called by Linux */
static void
dbus_usbos_ctlread_complete(CALLBACK_ARGS)
{
	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;

	ASSERT(urb);
	usbos_info = (usbos_info_t *)urb->context;

	dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_READ, urb->status);

#ifdef USBOS_THREAD
	if (usbos_info->rxctl_deferrespok) {
		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_CLASS |
		USB_RECIP_INTERFACE;
		usbos_info->ctl_read.bRequest = 1;
	}
#endif

	up(&usbos_info->ctl_lock);

	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
}

/** called by Linux */
static void
dbus_usbos_ctlwrite_complete(CALLBACK_ARGS)
{
	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;

	ASSERT(urb);
	usbos_info = (usbos_info_t *)urb->context;

	dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_WRITE, urb->status);

#ifdef USBOS_TX_THREAD
	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
#endif /* USBOS_TX_THREAD */

	up(&usbos_info->ctl_lock);

	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
}

#ifdef INTR_EP_ENABLE
/** called by Linux */
static void
dbus_usbos_intr_complete(CALLBACK_ARGS)
{
	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;
	bool killed = (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1 : 0;

	if (usbos_info == NULL || usbos_info->pub == NULL)
		return;
	if ((urb->status == -ENOENT && (!killed)) || urb->status == -ESHUTDOWN ||
		urb->status == -ENODEV) {
		dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
	}

	if (usbos_info->pub->busstate == DBUS_STATE_DOWN) {
		DBUSERR(("%s: intr cb when DBUS down, ignoring\n", __FUNCTION__));
		return;
	}
	dbus_usbos_ctl_complete(usbos_info, DBUS_CBINTR_POLL, urb->status);
}
#endif	/* INTR_EP_ENABLE */

/**
 * when the bus is going to sleep or halt, the Linux kernel requires us to take ownership of our
 * URBs again. Multiple code paths in this file require a list of URBs to be cancelled in a
 * concurrency save manner.
 */
static void
dbus_usbos_unlink(struct list_head *urbreq_q, spinlock_t *lock)
{
	urb_req_t *req;

	/* dbus_usbos_recv_complete() adds req back to req_freeq */
	while ((req = dbus_usbos_qdeq(urbreq_q, lock)) != NULL) {
		ASSERT(req->urb != NULL);
		USB_UNLINK_URB(req->urb);
	}
}

/** multiple code paths in this file require the bus to stop */
static void
dbus_usbos_cancel_all_urbs(usbos_info_t *usbos_info)
{
	int rxposted, txposted;

	DBUSTRACE(("%s: unlink all URBs\n", __FUNCTION__));

#ifdef USBOS_TX_THREAD
	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;

	/* Yield the CPU to TX thread so all pending requests are submitted */
	while (!list_empty(&usbos_info->usbos_tx_list)) {
		wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
		OSL_SLEEP(10);
	}
#endif /* USBOS_TX_THREAD */

	/* tell Linux kernel to cancel a single intr, ctl and blk URB */
	if (usbos_info->intr_urb)
		USB_UNLINK_URB(usbos_info->intr_urb);
	if (usbos_info->ctl_urb)
		USB_UNLINK_URB(usbos_info->ctl_urb);
	if (usbos_info->blk_urb)
		USB_UNLINK_URB(usbos_info->blk_urb);

	dbus_usbos_unlink(&usbos_info->req_txpostedq, &usbos_info->txposted_lock);
	dbus_usbos_unlink(&usbos_info->req_rxpostedq, &usbos_info->rxposted_lock);

	/* Wait until the callbacks for all submitted URBs have been called, because the
	 * handler needs to know is an USB suspend is in progress.
	 */
	SPINWAIT((atomic_read(&usbos_info->txposted) != 0 ||
		atomic_read(&usbos_info->rxposted) != 0), 10000);

	txposted = atomic_read(&usbos_info->txposted);
	rxposted = atomic_read(&usbos_info->rxposted);
	if (txposted != 0 || rxposted != 0) {
		DBUSERR(("%s ERROR: REQs posted, rx=%d tx=%d!\n",
			__FUNCTION__, rxposted, txposted));
	}
} /* dbus_usbos_cancel_all_urbs */

/** multiple code paths require the bus to stop */
static void
dbusos_stop(usbos_info_t *usbos_info)
{
	urb_req_t *req;
	int rxposted;
	req = NULL;
	BCM_REFERENCE(req);

	ASSERT(usbos_info);

	dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);

	dbus_usbos_cancel_all_urbs(usbos_info);

#ifdef USBOS_THREAD
	/* yield the CPU to rx packet thread */
	while (1) {
		if (atomic_read(&usbos_info->usbos_list_cnt) <= 0)	break;
		wake_up_interruptible(&usbos_info->usbos_queue_head);
		OSL_SLEEP(3);
	}
#endif /* USBOS_THREAD */

	rxposted = atomic_read(&usbos_info->rxposted);
	if (rxposted > 0) {
		DBUSERR(("%s ERROR: rx REQs posted=%d in stop!\n", __FUNCTION__,
			rxposted));
	}

	ASSERT(atomic_read(&usbos_info->txposted) == 0 && rxposted == 0);

} /* dbusos_stop */

#if defined(USB_SUSPEND_AVAILABLE)

/**
 * Linux kernel sports a 'USB auto suspend' feature. See: http://lwn.net/Articles/373550/
 * The suspend method is called by the Linux kernel to warn the driver that the device is going to
 * be suspended.  If the driver returns a negative error code, the suspend will be aborted. If the
 * driver returns 0, it must cancel all outstanding URBs (usb_kill_urb()) and not submit any more.
 */
static int
dbus_usbos_suspend(struct usb_interface *intf,
            pm_message_t message)
{
	DBUSERR(("%s suspend state: %d\n", __FUNCTION__, g_probe_info.suspend_state));
	/* DHD for full dongle model */
	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPEND_PENDING;
	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_SLEEP);
	dbus_usbos_cancel_all_urbs((usbos_info_t*)g_probe_info.usbos_info);
	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPENDED;

	return 0;
}

/**
 * The resume method is called to tell the driver that the device has been resumed and the driver
 * can return to normal operation.  URBs may once more be submitted.
 */
static int dbus_usbos_resume(struct usb_interface *intf)
{
	DBUSERR(("%s Device resumed\n", __FUNCTION__));

	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_UP);
	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
	return 0;
}

/**
* This function is directly called by the Linux kernel, when the suspended device has been reset
* instead of being resumed
*/
static int dbus_usbos_reset_resume(struct usb_interface *intf)
{
	DBUSERR(("%s Device reset resumed\n", __FUNCTION__));

	/* The device may have lost power, so a firmware download may be required */
	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_DL_NEEDED);
	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
	return 0;
}

#endif /* USB_SUSPEND_AVAILABLE */

/**
 * Called by Linux kernel at initialization time, kernel wants to know if our driver will accept the
 * caller supplied USB interface. Note that USB drivers are bound to interfaces, and not to USB
 * devices.
 */
#ifdef KERNEL26
#define DBUS_USBOS_PROBE() static int dbus_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id)
#define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_interface *intf)
#else
#define DBUS_USBOS_PROBE() static void * dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum, const struct usb_device_id *id)
#define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr)
#endif /* KERNEL26 */

DBUS_USBOS_PROBE()
{
	int ep;
	struct usb_endpoint_descriptor *endpoint;
	int ret = 0;
#ifdef KERNEL26
	struct usb_device *usb = interface_to_usbdev(intf);
#else
	int claimed = 0;
#endif
	int num_of_eps;
#ifdef BCMUSBDEV_COMPOSITE
	int wlan_if = -1;
	bool intr_ep = FALSE;
#endif /* BCMUSBDEV_COMPOSITE */
	wifi_adapter_info_t *adapter;

	DHD_MUTEX_LOCK();

	DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
		usb->bus->busnum, usb->portnum));
	adapter = dhd_wifi_platform_attach_adapter(USB_BUS, usb->bus->busnum,
		usb->portnum, WIFI_STATUS_POWER_ON);
	if (adapter == NULL) {
		DBUSERR(("%s: can't find adapter info for this chip\n", __FUNCTION__));
		goto fail;
	}

#ifdef BCMUSBDEV_COMPOSITE
	wlan_if = dbus_usbos_intf_wlan(usb);
#ifdef KERNEL26
	if ((wlan_if >= 0) && (IFPTR(usb, wlan_if) == intf))
#else
	if (wlan_if == ifnum)
#endif /* KERNEL26 */
	{
#endif /* BCMUSBDEV_COMPOSITE */
		g_probe_info.usb = usb;
		g_probe_info.dldone = TRUE;
#ifdef BCMUSBDEV_COMPOSITE
	} else {
		DBUSTRACE(("dbus_usbos_probe: skip probe for non WLAN interface\n"));
		ret = BCME_UNSUPPORTED;
		goto fail;
	}
#endif /* BCMUSBDEV_COMPOSITE */

#ifdef KERNEL26
	g_probe_info.intf = intf;
#endif /* KERNEL26 */

#ifdef BCMUSBDEV_COMPOSITE
	if (IFDESC(usb, wlan_if).bInterfaceNumber > USB_COMPIF_MAX)
#else
	if (IFDESC(usb, CONTROL_IF).bInterfaceNumber)
#endif /* BCMUSBDEV_COMPOSITE */
	{
		ret = -1;
		goto fail;
	}
	if (id != NULL) {
		g_probe_info.vid = id->idVendor;
		g_probe_info.pid = id->idProduct;
	}

#ifdef KERNEL26
	usb_set_intfdata(intf, &g_probe_info);
#endif

	/* Check that the device supports only one configuration */
	if (usb->descriptor.bNumConfigurations != 1) {
		ret = -1;
		goto fail;
	}

	if (usb->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) {
#ifdef BCMUSBDEV_COMPOSITE
		if ((usb->descriptor.bDeviceClass != USB_CLASS_MISC) &&
			(usb->descriptor.bDeviceClass != USB_CLASS_WIRELESS)) {
#endif /* BCMUSBDEV_COMPOSITE */
			ret = -1;
			goto fail;
#ifdef BCMUSBDEV_COMPOSITE
		}
#endif /* BCMUSBDEV_COMPOSITE */
	}

	/*
	 * Only the BDC interface configuration is supported:
	 *	Device class: USB_CLASS_VENDOR_SPEC
	 *	if0 class: USB_CLASS_VENDOR_SPEC
	 *	if0/ep0: control
	 *	if0/ep1: bulk in
	 *	if0/ep2: bulk out (ok if swapped with bulk in)
	 */
	if (CONFIGDESC(usb)->bNumInterfaces != 1) {
#ifdef BCMUSBDEV_COMPOSITE
		if (CONFIGDESC(usb)->bNumInterfaces > USB_COMPIF_MAX) {
#endif /* BCMUSBDEV_COMPOSITE */
			ret = -1;
			goto fail;
#ifdef BCMUSBDEV_COMPOSITE
		}
#endif /* BCMUSBDEV_COMPOSITE */
	}

	/* Check interface */
#ifndef KERNEL26
#ifdef BCMUSBDEV_COMPOSITE
	if (usb_interface_claimed(IFPTR(usb, wlan_if)))
#else
	if (usb_interface_claimed(IFPTR(usb, CONTROL_IF)))
#endif /* BCMUSBDEV_COMPOSITE */
	{
		ret = -1;
		goto fail;
	}
#endif /* !KERNEL26 */

#ifdef BCMUSBDEV_COMPOSITE
	if ((IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
		IFDESC(usb, wlan_if).bInterfaceSubClass != 2 ||
		IFDESC(usb, wlan_if).bInterfaceProtocol != 0xff) &&
		(IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_MISC ||
		IFDESC(usb, wlan_if).bInterfaceSubClass != USB_SUBCLASS_COMMON ||
		IFDESC(usb, wlan_if).bInterfaceProtocol != USB_PROTO_IAD))
#else
	if (IFDESC(usb, CONTROL_IF).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
		IFDESC(usb, CONTROL_IF).bInterfaceSubClass != 2 ||
		IFDESC(usb, CONTROL_IF).bInterfaceProtocol != 0xff)
#endif /* BCMUSBDEV_COMPOSITE */
	{
#ifdef BCMUSBDEV_COMPOSITE
			DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n",
				__FUNCTION__,
				IFDESC(usb, wlan_if).bInterfaceClass,
				IFDESC(usb, wlan_if).bInterfaceSubClass,
				IFDESC(usb, wlan_if).bInterfaceProtocol));
#else
			DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n",
				__FUNCTION__,
				IFDESC(usb, CONTROL_IF).bInterfaceClass,
				IFDESC(usb, CONTROL_IF).bInterfaceSubClass,
				IFDESC(usb, CONTROL_IF).bInterfaceProtocol));
#endif /* BCMUSBDEV_COMPOSITE */
			ret = -1;
			goto fail;
	}

	/* Check control endpoint */
#ifdef BCMUSBDEV_COMPOSITE
	endpoint = &IFEPDESC(usb, wlan_if, 0);
#else
	endpoint = &IFEPDESC(usb, CONTROL_IF, 0);
#endif /* BCMUSBDEV_COMPOSITE */
	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) {
#ifdef BCMUSBDEV_COMPOSITE
		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
			USB_ENDPOINT_XFER_BULK) {
#endif /* BCMUSBDEV_COMPOSITE */
			DBUSERR(("%s: invalid control endpoint %d\n",
				__FUNCTION__, endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK));
			ret = -1;
			goto fail;
#ifdef BCMUSBDEV_COMPOSITE
		}
#endif /* BCMUSBDEV_COMPOSITE */
	}

#ifdef BCMUSBDEV_COMPOSITE
	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) {
#endif /* BCMUSBDEV_COMPOSITE */
		g_probe_info.intr_pipe =
			usb_rcvintpipe(usb, endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
#ifdef BCMUSBDEV_COMPOSITE
		intr_ep = TRUE;
	}
#endif /* BCMUSBDEV_COMPOSITE */

#ifndef KERNEL26
	/* Claim interface */
#ifdef BCMUSBDEV_COMPOSITE
	usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, wlan_if), &g_probe_info);
#else
	usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF), &g_probe_info);
#endif /* BCMUSBDEV_COMPOSITE */
	claimed = 1;
#endif /* !KERNEL26 */
	g_probe_info.rx_pipe = 0;
	g_probe_info.rx_pipe2 = 0;
	g_probe_info.tx_pipe = 0;
#ifdef BCMUSBDEV_COMPOSITE
	if (intr_ep)
		ep = 1;
	else
		ep = 0;
	num_of_eps = IFDESC(usb, wlan_if).bNumEndpoints - 1;
#else
	num_of_eps = IFDESC(usb, BULK_IF).bNumEndpoints - 1;
#endif /* BCMUSBDEV_COMPOSITE */

	if ((num_of_eps != 2) && (num_of_eps != 3)) {
#ifdef BCMUSBDEV_COMPOSITE
		if (num_of_eps > 7)
#endif /* BCMUSBDEV_COMPOSITE */
			ASSERT(0);
	}
	/* Check data endpoints and get pipes */
#ifdef BCMUSBDEV_COMPOSITE
	for (; ep <= num_of_eps; ep++)
#else
	for (ep = 1; ep <= num_of_eps; ep++)
#endif /* BCMUSBDEV_COMPOSITE */
	{
#ifdef BCMUSBDEV_COMPOSITE
		endpoint = &IFEPDESC(usb, wlan_if, ep);
#else
		endpoint = &IFEPDESC(usb, BULK_IF, ep);
#endif /* BCMUSBDEV_COMPOSITE */
		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
		    USB_ENDPOINT_XFER_BULK) {
			DBUSERR(("%s: invalid data endpoint %d\n",
			           __FUNCTION__, ep));
			ret = -1;
			goto fail;
		}

		if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) {
			/* direction: dongle->host */
			if (!g_probe_info.rx_pipe) {
				g_probe_info.rx_pipe = usb_rcvbulkpipe(usb,
					(endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK));
			} else {
				g_probe_info.rx_pipe2 = usb_rcvbulkpipe(usb,
					(endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK));
			}

		} else
			g_probe_info.tx_pipe = usb_sndbulkpipe(usb, (endpoint->bEndpointAddress &
			     USB_ENDPOINT_NUMBER_MASK));
	}

	/* Allocate interrupt URB and data buffer */
	/* RNDIS says 8-byte intr, our old drivers used 4-byte */
#ifdef BCMUSBDEV_COMPOSITE
	g_probe_info.intr_size = (IFEPDESC(usb, wlan_if, 0).wMaxPacketSize == 16) ? 8 : 4;
	g_probe_info.interval = IFEPDESC(usb, wlan_if, 0).bInterval;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21))
	usb->quirks |= USB_QUIRK_NO_SET_INTF;
#endif
#else
	g_probe_info.intr_size = (IFEPDESC(usb, CONTROL_IF, 0).wMaxPacketSize == 16) ? 8 : 4;
	g_probe_info.interval = IFEPDESC(usb, CONTROL_IF, 0).bInterval;
#endif /* BCMUSBDEV_COMPOSITE */

#ifndef KERNEL26
	/* usb_fill_int_urb does the interval decoding in 2.6 */
	if (usb->speed == USB_SPEED_HIGH)
		g_probe_info.interval = 1 << (g_probe_info.interval - 1);
#endif
	if (usb->speed == USB_SPEED_SUPER) {
		g_probe_info.device_speed = SUPER_SPEED;
		DBUSERR(("super speed device detected\n"));
	} else if (usb->speed == USB_SPEED_HIGH) {
		g_probe_info.device_speed = HIGH_SPEED;
		DBUSERR(("high speed device detected\n"));
	} else {
		g_probe_info.device_speed = FULL_SPEED;
		DBUSERR(("full speed device detected\n"));
	}
	if (g_probe_info.dereged == FALSE && probe_cb) {
		disc_arg = probe_cb(probe_arg, "", USB_BUS, usb->bus->busnum, usb->portnum, 0);
	}

	g_probe_info.disc_cb_done = FALSE;

#ifdef KERNEL26
	intf->needs_remote_wakeup = 1;
#endif /* KERNEL26 */
	DHD_MUTEX_UNLOCK();

	/* Success */
#ifdef KERNEL26
	return DBUS_OK;
#else
	usb_inc_dev_use(usb);
	return &g_probe_info;
#endif

fail:
	printf("%s: Exit ret=%d\n", __FUNCTION__, ret);
#ifdef BCMUSBDEV_COMPOSITE
	if (ret != BCME_UNSUPPORTED)
#endif /* BCMUSBDEV_COMPOSITE */
		DBUSERR(("%s: failed with errno %d\n", __FUNCTION__, ret));
#ifndef KERNEL26
	if (claimed)
#ifdef BCMUSBDEV_COMPOSITE
		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
#else
		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
#endif /* BCMUSBDEV_COMPOSITE */
#endif /* !KERNEL26 */

	DHD_MUTEX_UNLOCK();
#ifdef KERNEL26
	usb_set_intfdata(intf, NULL);
	return ret;
#else
	return NULL;
#endif
} /* dbus_usbos_probe */

/** Called by Linux kernel, is the counter part of dbus_usbos_probe() */
DBUS_USBOS_DISCONNECT()
{
#ifdef KERNEL26
	struct usb_device *usb = interface_to_usbdev(intf);
	probe_info_t *probe_usb_init_data = usb_get_intfdata(intf);
#else
	probe_info_t *probe_usb_init_data = (probe_info_t *) ptr;
#endif
	usbos_info_t *usbos_info;

	DHD_MUTEX_LOCK();

	DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
		usb->bus->busnum, usb->portnum));

	if (probe_usb_init_data) {
		usbos_info = (usbos_info_t *) probe_usb_init_data->usbos_info;
		if (usbos_info) {
			if ((probe_usb_init_data->dereged == FALSE) && disconnect_cb && disc_arg) {
				disconnect_cb(disc_arg);
				disc_arg = NULL;
				probe_usb_init_data->disc_cb_done = TRUE;
			}
		}
	}

	if (usb) {
#ifndef KERNEL26
#ifdef BCMUSBDEV_COMPOSITE
		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
#else
		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
#endif /* BCMUSBDEV_COMPOSITE */
		usb_dec_dev_use(usb);
#endif /* !KERNEL26 */
	}
	DHD_MUTEX_UNLOCK();
} /* dbus_usbos_disconnect */

#define LOOPBACK_PKT_START 0xBABE1234

bool is_loopback_pkt(void *buf)
{

	uint32 *buf_ptr = (uint32 *) buf;

	if (*buf_ptr == LOOPBACK_PKT_START)
		return TRUE;
	return FALSE;

}

int matches_loopback_pkt(void *buf)
{
	int i, j;
	unsigned char *cbuf = (unsigned char *) buf;

	for (i = 4; i < loopback_size; i++) {
		if (cbuf[i] != (i % 256)) {
			printf("%s: mismatch at i=%d %d : ", __FUNCTION__, i, cbuf[i]);
			for (j = i; ((j < i+ 16) && (j < loopback_size)); j++) {
				printf("%d ", cbuf[j]);
			}
			printf("\n");
			return 0;
		}
	}
	loopback_rx_cnt++;
	return 1;
}

int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size)
{
	usbos_info_t *usbos_info = (usbos_info_t *) usbos_info_ptr;
	unsigned char *buf;
	int j;
	void* p = NULL;
	int rc, last_rx_cnt;
	int tx_failed_cnt;
	int max_size = 1650;
	int usb_packet_size = 512;
	int min_packet_size = 10;

	if (size % usb_packet_size == 0) {
		size = size - 1;
		DBUSERR(("%s: overriding size=%d \n", __FUNCTION__, size));
	}

	if (size < min_packet_size) {
		size = min_packet_size;
		DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, min_packet_size));
	}
	if (size > max_size) {
		size = max_size;
		DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, max_size));
	}

	loopback_tx_cnt = 0;
	loopback_rx_cnt = 0;
	tx_failed_cnt = 0;
	loopback_size   = size;

	while (loopback_tx_cnt < cnt) {
		uint32 *x;
		int pkt_size = loopback_size;

		p = PKTGET(usbos_info->pub->osh, pkt_size, TRUE);
		if (p == NULL) {
			DBUSERR(("%s:%d Failed to allocate packet sz=%d\n",
			       __FUNCTION__, __LINE__, pkt_size));
			return BCME_ERROR;
		}
		x = (uint32*) PKTDATA(usbos_info->pub->osh, p);
		*x = LOOPBACK_PKT_START;
		buf = (unsigned char*) x;
		for (j = 4; j < pkt_size; j++) {
			buf[j] = j % 256;
		}
		rc = dbus_send_buf(usbos_info->pub, buf, pkt_size, p);
		if (rc != BCME_OK) {
			DBUSERR(("%s:%d Freeing packet \n", __FUNCTION__, __LINE__));
			PKTFREE(usbos_info->pub->osh, p, TRUE);
			dbus_usbos_wait(usbos_info, 1);
			tx_failed_cnt++;
		} else {
			loopback_tx_cnt++;
			tx_failed_cnt = 0;
		}
		if (tx_failed_cnt == 5) {
			DBUSERR(("%s : Failed to send loopback packets cnt=%d loopback_tx_cnt=%d\n",
			 __FUNCTION__, cnt, loopback_tx_cnt));
			break;
		}
	}
	printf("Transmitted %d loopback packets of size %d\n", loopback_tx_cnt, loopback_size);

	last_rx_cnt = loopback_rx_cnt;
	while (loopback_rx_cnt < loopback_tx_cnt) {
		dbus_usbos_wait(usbos_info, 1);
		if (loopback_rx_cnt <= last_rx_cnt) {
			DBUSERR(("%s: Matched rx cnt stuck at %d \n", __FUNCTION__, last_rx_cnt));
			return BCME_ERROR;
		}
		last_rx_cnt = loopback_rx_cnt;
	}
	printf("Received %d loopback packets of size %d\n", loopback_tx_cnt, loopback_size);

	return BCME_OK;
} /* dbus_usbos_loopback_tx */

/**
 * Higher layer (dbus_usb.c) wants to transmit an I/O Request Block
 *     @param[in] txirb txirb->pkt, if non-zero, contains a single or a chain of packets
 */
static int
dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	urb_req_t *req, *req_zlp = NULL;
	int ret = DBUS_OK;
	unsigned long flags;
	void *pkt;
	uint32 buffer_length;
	uint8 *buf;

	if ((usbos_info == NULL) || !usbos_info->tx_pipe) {
		return DBUS_ERR;
	}

	if (txirb->pkt != NULL) {
		buffer_length = pkttotlen(usbos_info->pub->osh, txirb->pkt);
		/* In case of multiple packets the values below may be overwritten */
		txirb->send_buf = NULL;
		buf = PKTDATA(usbos_info->pub->osh, txirb->pkt);
	} else { /* txirb->buf != NULL */
		ASSERT(txirb->buf != NULL);
		ASSERT(txirb->send_buf == NULL);
		buffer_length = txirb->len;
		buf = txirb->buf;
	}

	if (!(req = dbus_usbos_qdeq(&usbos_info->req_txfreeq, &usbos_info->txfree_lock))) {
		DBUSERR(("%s No free URB!\n", __FUNCTION__));
		return DBUS_ERR_TXDROP;
	}

	/* If not using standard Linux kernel functionality for handling Zero Length Packet(ZLP),
	 * the dbus needs to generate ZLP when length is multiple of MaxPacketSize.
	 */
#ifndef WL_URB_ZPKT
	if (!(buffer_length % usbos_info->maxps)) {
		if (!(req_zlp =
			dbus_usbos_qdeq(&usbos_info->req_txfreeq, &usbos_info->txfree_lock))) {
			DBUSERR(("%s No free URB for ZLP!\n", __FUNCTION__));
			dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
			return DBUS_ERR_TXDROP;
		}

		/* No txirb, so that dbus_usbos_send_complete can differentiate between
		 * DATA and ZLP.
		 */
		req_zlp->arg = NULL;
		req_zlp->usbinfo = usbos_info;
		req_zlp->buf_len = 0;

		usb_fill_bulk_urb(req_zlp->urb, usbos_info->usb, usbos_info->tx_pipe, NULL,
			0, (usb_complete_t)dbus_usbos_send_complete, req_zlp);

		req_zlp->urb->transfer_flags |= URB_QUEUE_BULK;
	}
#endif /* !WL_URB_ZPKT */

#ifndef USBOS_TX_THREAD
	/* Disable USB autosuspend until this request completes, request USB resume if needed.
	 * Because this call runs asynchronously, there is no guarantee the bus is resumed before
	 * the URB is submitted, and the URB might be dropped. Use USBOS_TX_THREAD to avoid
	 * this.
	 */
	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* !USBOS_TX_THREAD */

	spin_lock_irqsave(&usbos_info->txlock, flags);

	req->arg = txirb;
	req->usbinfo = usbos_info;
	req->buf_len = 0;

	/* Prepare the URB */
	if (txirb->pkt != NULL) {
		uint32 pktlen;
		uint8 *transfer_buf;

		/* For multiple packets, allocate contiguous buffer and copy packet data to it */
		if (PKTNEXT(usbos_info->pub->osh, txirb->pkt)) {
			transfer_buf = MALLOC(usbos_info->pub->osh, buffer_length);
			if (!transfer_buf) {
				ret = DBUS_ERR_TXDROP;
				DBUSERR(("fail to alloc to usb buffer\n"));
				goto fail;
			}

			pkt = txirb->pkt;
			txirb->send_buf = transfer_buf;
			req->buf_len = buffer_length;

			while (pkt) {
				pktlen = PKTLEN(usbos_info->pub->osh, pkt);
				bcopy(PKTDATA(usbos_info->pub->osh, pkt), transfer_buf, pktlen);
				transfer_buf += pktlen;
				pkt = PKTNEXT(usbos_info->pub->osh, pkt);
			}

			ASSERT(((uint8 *) txirb->send_buf + buffer_length) == transfer_buf);

			/* Overwrite buf pointer with pointer to allocated contiguous transfer_buf
			 */
			buf = txirb->send_buf;
		}
	}

	usb_fill_bulk_urb(req->urb, usbos_info->usb, usbos_info->tx_pipe, buf,
		buffer_length, (usb_complete_t)dbus_usbos_send_complete, req);

	req->urb->transfer_flags |= URB_QUEUE_BULK;

#ifdef USBOS_TX_THREAD
	/* Enqueue TX request, the TX thread will resume the bus if needed and submit
	 * it asynchronously
	 */
	dbus_usbos_qenq(&usbos_info->usbos_tx_list, req, &usbos_info->usbos_tx_list_lock);
	if (req_zlp != NULL) {
		dbus_usbos_qenq(&usbos_info->usbos_tx_list, req_zlp,
			&usbos_info->usbos_tx_list_lock);
	}
	spin_unlock_irqrestore(&usbos_info->txlock, flags);

	wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
	return DBUS_OK;
#else
	if ((ret = USB_SUBMIT_URB(req->urb))) {
		ret = DBUS_ERR_TXDROP;
		goto fail;
	}

	dbus_usbos_qenq(&usbos_info->req_txpostedq, req, &usbos_info->txposted_lock);
	atomic_inc(&usbos_info->txposted);

	if (req_zlp != NULL) {
		if ((ret = USB_SUBMIT_URB(req_zlp->urb))) {
			DBUSERR(("failed to submit ZLP URB!\n"));
			ASSERT(0);
			ret = DBUS_ERR_TXDROP;
			goto fail2;
		}

		dbus_usbos_qenq(&usbos_info->req_txpostedq, req_zlp, &usbos_info->txposted_lock);
		/* Also increment txposted for zlp packet, as it will be decremented in
		 * dbus_usbos_send_complete()
		 */
		atomic_inc(&usbos_info->txposted);
	}

	spin_unlock_irqrestore(&usbos_info->txlock, flags);
	return DBUS_OK;
#endif /* USBOS_TX_THREAD */

fail:
	if (txirb->send_buf != NULL) {
		MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
		txirb->send_buf = NULL;
		req->buf_len = 0;
	}
	dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
#ifndef USBOS_TX_THREAD
fail2:
#endif
	if (req_zlp != NULL) {
		dbus_usbos_qenq(&usbos_info->req_txfreeq, req_zlp, &usbos_info->txfree_lock);
	}

	spin_unlock_irqrestore(&usbos_info->txlock, flags);

#ifndef USBOS_TX_THREAD
	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* !USBOS_TX_THREAD */

	return ret;
} /* dbus_usbos_intf_send_irb */

/** Higher layer (dbus_usb.c) recycles a received (and used) packet. */
static int
dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	int ret = DBUS_OK;

	if (usbos_info == NULL)
		return DBUS_ERR;

	ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, 0);
	return ret;
}

static int
dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb, uint32 ep_idx)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	int ret = DBUS_OK;

	if (usbos_info == NULL)
		return DBUS_ERR;

#ifdef INTR_EP_ENABLE
		/* By specifying the ep_idx value of 0xff, the cdc layer is asking to
		* submit an interrupt URB
		*/
		if (rxirb == NULL && ep_idx == 0xff) {
			/* submit intr URB */
			if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb)) < 0) {
				DBUSERR(("%s intr USB_SUBMIT_URB failed, status %d\n",
					__FUNCTION__, ret));
			}
			return ret;
		}
#else
		if (rxirb == NULL) {
			return DBUS_ERR;
		}
#endif /* INTR_EP_ENABLE */

	ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, ep_idx);
	return ret;
}

/** Higher layer (dbus_usb.c) want to cancel an IRB */
static int
dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if (usbos_info == NULL)
		return DBUS_ERR;

	return DBUS_ERR;
}

/** Only one CTL transfer can be pending at any time. This function may block. */
static int
dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	uint16 size;
#ifndef USBOS_TX_THREAD
	int status;
#endif /* USBOS_TX_THREAD */

	if ((usbos_info == NULL) || (buf == NULL) || (len == 0))
		return DBUS_ERR;

	if (usbos_info->ctl_urb == NULL)
		return DBUS_ERR;

	/* Block until a pending CTL transfer has completed */
	if (down_interruptible(&usbos_info->ctl_lock) != 0) {
		return DBUS_ERR_TXCTLFAIL;
	}

#ifdef USBOS_TX_THREAD
	ASSERT(usbos_info->ctl_state == USBOS_REQUEST_STATE_UNSCHEDULED);
#else
	/* Disable USB autosuspend until this request completes, request USB resume if needed.
	 * Because this call runs asynchronously, there is no guarantee the bus is resumed before
	 * the URB is submitted, and the URB might be dropped. Use USBOS_TX_THREAD to avoid
	 * this.
	 */
	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
#endif /* USBOS_TX_THREAD */

	size = len;
	usbos_info->ctl_write.wLength = cpu_to_le16p(&size);
	usbos_info->ctl_urb->transfer_buffer_length = size;

	usb_fill_control_urb(usbos_info->ctl_urb,
		usbos_info->usb,
		usb_sndctrlpipe(usbos_info->usb, 0),
		(unsigned char *) &usbos_info->ctl_write,
		buf, size, (usb_complete_t)dbus_usbos_ctlwrite_complete, usbos_info);

#ifdef USBOS_TX_THREAD
	/* Enqueue CTRL request for transmission by the TX thread. The
	 * USB bus will first be resumed if needed.
	 */
	usbos_info->ctl_state = USBOS_REQUEST_STATE_SCHEDULED;
	wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
#else
	status = USB_SUBMIT_URB(usbos_info->ctl_urb);
	if (status < 0) {
		DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
		up(&usbos_info->ctl_lock);

		USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

		return DBUS_ERR_TXCTLFAIL;
	}
#endif /* USBOS_TX_THREAD */

	return DBUS_OK;
} /* dbus_usbos_intf_send_ctl */

/** This function does not seem to be called by anyone, including dbus_usb.c */
static int
dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	int status;
	uint16 size;

	if ((usbos_info == NULL) || (buf == NULL) || (len == 0))
		return DBUS_ERR;

	if (usbos_info->ctl_urb == NULL)
		return DBUS_ERR;

	/* Block until a pending CTRL transfer has completed */
	if (down_interruptible(&usbos_info->ctl_lock) != 0) {
		return DBUS_ERR_TXCTLFAIL;
	}

	/* Disable USB autosuspend until this request completes, request USB resume if needed. */
	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);

	size = len;
	usbos_info->ctl_read.wLength = cpu_to_le16p(&size);
	usbos_info->ctl_urb->transfer_buffer_length = size;

	if (usbos_info->rxctl_deferrespok) {
		/* BMAC model */
		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR |
			USB_RECIP_INTERFACE;
		usbos_info->ctl_read.bRequest = DL_DEFER_RESP_OK;
	} else {
		/* full dongle model */
		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_CLASS |
			USB_RECIP_INTERFACE;
		usbos_info->ctl_read.bRequest = 1;
	}

	usb_fill_control_urb(usbos_info->ctl_urb,
		usbos_info->usb,
		usb_rcvctrlpipe(usbos_info->usb, 0),
		(unsigned char *) &usbos_info->ctl_read,
		buf, size, (usb_complete_t)dbus_usbos_ctlread_complete, usbos_info);

	status = USB_SUBMIT_URB(usbos_info->ctl_urb);
	if (status < 0) {
		DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
		up(&usbos_info->ctl_lock);

		USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);

		return DBUS_ERR_RXCTLFAIL;
	}

	return DBUS_OK;
}

static int
dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if ((usbos_info == NULL) || (attrib == NULL))
		return DBUS_ERR;

	attrib->bustype = DBUS_USB;
	attrib->vid = g_probe_info.vid;
	attrib->pid = g_probe_info.pid;
	attrib->devid = 0x4322;

	attrib->nchan = 1;

	/* MaxPacketSize for USB hi-speed bulk out is 512 bytes
	 * and 64-bytes for full-speed.
	 * When sending pkt > MaxPacketSize, Host SW breaks it
	 * up into multiple packets.
	 */
	attrib->mtu = usbos_info->maxps;

	return DBUS_OK;
}

/** Called by higher layer (dbus_usb.c) when it wants to 'up' the USB interface to the dongle */
static int
dbus_usbos_intf_up(void *bus)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	uint16 ifnum;
#ifdef BCMUSBDEV_COMPOSITE
	int wlan_if = 0;
#endif
	if (usbos_info == NULL)
		return DBUS_ERR;

	if (usbos_info->usb == NULL)
		return DBUS_ERR;

#if defined(INTR_EP_ENABLE)
	/* full dongle use intr EP, bmac doesn't use it */
	if (usbos_info->intr_urb) {
		int ret;

		usb_fill_int_urb(usbos_info->intr_urb, usbos_info->usb,
			usbos_info->intr_pipe, &usbos_info->intr,
			usbos_info->intr_size, (usb_complete_t)dbus_usbos_intr_complete,
			usbos_info, usbos_info->interval);

		if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb))) {
			DBUSERR(("%s USB_SUBMIT_URB failed with status %d\n", __FUNCTION__, ret));
			return DBUS_ERR;
		}
	}
#endif	

	if (usbos_info->ctl_urb) {
		usbos_info->ctl_in_pipe = usb_rcvctrlpipe(usbos_info->usb, 0);
		usbos_info->ctl_out_pipe = usb_sndctrlpipe(usbos_info->usb, 0);

#ifdef BCMUSBDEV_COMPOSITE
		wlan_if = dbus_usbos_intf_wlan(usbos_info->usb);
		ifnum = cpu_to_le16(IFDESC(usbos_info->usb, wlan_if).bInterfaceNumber);
#else
		ifnum = cpu_to_le16(IFDESC(usbos_info->usb, CONTROL_IF).bInterfaceNumber);
#endif /* BCMUSBDEV_COMPOSITE */
		/* CTL Write */
		usbos_info->ctl_write.bRequestType =
			USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
		usbos_info->ctl_write.bRequest = 0;
		usbos_info->ctl_write.wValue = cpu_to_le16(0);
		usbos_info->ctl_write.wIndex = cpu_to_le16p(&ifnum);

		/* CTL Read */
		usbos_info->ctl_read.bRequestType =
			USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
		usbos_info->ctl_read.bRequest = 1;
		usbos_info->ctl_read.wValue = cpu_to_le16(0);
		usbos_info->ctl_read.wIndex = cpu_to_le16p(&ifnum);
	}

	/* Success, indicate usbos_info is fully up */
	dbus_usbos_state_change(usbos_info, DBUS_STATE_UP);

	return DBUS_OK;
} /* dbus_usbos_intf_up */

static int
dbus_usbos_intf_down(void *bus)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if (usbos_info == NULL)
		return DBUS_ERR;

	dbusos_stop(usbos_info);
	return DBUS_OK;
}

static int
dbus_usbos_intf_stop(void *bus)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if (usbos_info == NULL)
		return DBUS_ERR;

	dbusos_stop(usbos_info);
	return DBUS_OK;
}


/** Called by higher layer (dbus_usb.c) */
static int
dbus_usbos_intf_set_config(void *bus, dbus_config_t *config)
{
	int err = DBUS_ERR;
	usbos_info_t* usbos_info = bus;

	if (config->config_id == DBUS_CONFIG_ID_RXCTL_DEFERRES) {
		usbos_info->rxctl_deferrespok = config->rxctl_deferrespok;
		err = DBUS_OK;
	} else if (config->config_id == DBUS_CONFIG_ID_AGGR_LIMIT) {
		/* DBUS_CONFIG_ID_AGGR_LIMIT shouldn't be called after probe stage */
		ASSERT(disc_arg == NULL);
		ASSERT(config->aggr_param.maxrxsf > 0);
		ASSERT(config->aggr_param.maxrxsize > 0);
		if (config->aggr_param.maxrxsize > usbos_info->rxbuf_len) {
			int state = usbos_info->pub->busstate;
			dbus_usbos_unlink(&usbos_info->req_rxpostedq, &usbos_info->rxposted_lock);
			while (atomic_read(&usbos_info->rxposted)) {
				DBUSTRACE(("%s rxposted is %d, delay 1 ms\n", __FUNCTION__,
					atomic_read(&usbos_info->rxposted)));
				dbus_usbos_wait(usbos_info, 1);
			}
			usbos_info->rxbuf_len = config->aggr_param.maxrxsize;
			dbus_usbos_state_change(usbos_info, state);
		}
		err = DBUS_OK;
	}

	return err;
}


/** Called by dbus_usb.c when it wants to download firmware into the dongle */
bool
dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer, int buflen)
{
	int transferred;
	int index = 0;
	char *tmpbuf;

	if ((usbinfo == NULL) || (buffer == NULL) || (buflen == 0))
		return FALSE;

	tmpbuf = (char *) MALLOC(usbinfo->pub->osh, buflen);
	if (!tmpbuf) {
		DBUSERR(("%s: Unable to allocate memory \n", __FUNCTION__));
		return FALSE;
	}

#ifdef BCM_REQUEST_FW
	if (cmd == DL_GO) {
		index = 1;
	}
#endif

	/* Disable USB autosuspend until this request completes, request USB resume if needed. */
	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0),
		cmd, (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
		0, index,
		(void*) tmpbuf, buflen, USB_CTRL_EP_TIMEOUT);
	if (transferred == buflen) {
		memcpy(buffer, tmpbuf, buflen);
	} else {
		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
	}

	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

	MFREE(usbinfo->pub->osh, tmpbuf, buflen);
	return (transferred == buflen);
}

/**
 * Called by dbus_usb.c when it wants to download a buffer into the dongle (e.g. as part of the
 * download process, when writing nvram variables).
 */
int
dbus_write_membytes(usbos_info_t* usbinfo, bool set, uint32 address, uint8 *data, uint size)
{
	hwacc_t hwacc;
	int write_bytes = 4;
	int status;
	int retval = 0;

	DBUSTRACE(("Enter:%s\n", __FUNCTION__));

	/* Read is not supported */
	if (set == 0) {
		DBUSERR(("Currently read is not supported!!\n"));
		return -1;
	}

	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

	hwacc.cmd = DL_CMD_WRHW;
	hwacc.addr = address;

	DBUSTRACE(("Address:%x size:%d", hwacc.addr, size));
	do {
		if (size >= 4) {
			write_bytes = 4;
		} else if (size >= 2) {
			write_bytes = 2;
		} else {
			write_bytes = 1;
		}

		hwacc.len = write_bytes;

		while (size >= write_bytes) {
			hwacc.data = *((unsigned int*)data);

			status = USB_CONTROL_MSG(usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0),
				DL_WRHW, (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
				1, 0, (char *)&hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);

			if (status < 0) {
				retval = -1;
				DBUSERR((" Ctrl write hwacc failed w/status %d @ address:%x \n",
					status, hwacc.addr));
				goto err;
			}

			hwacc.addr += write_bytes;
			data += write_bytes;
			size -= write_bytes;
		}
	} while (size > 0);

err:
	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

	return retval;
}

int
dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen, uint32 *value)
{
	usbos_info_t *usbinfo = (usbos_info_t *) bus;
	int ret = DBUS_OK;
	int transferred;
	uint32 cmd;
	hwacc_t	hwacc;

	if (usbinfo == NULL)
		return DBUS_ERR;

	if (datalen == 1)
		cmd = DL_RDHW8;
	else if (datalen == 2)
		cmd = DL_RDHW16;
	else
		cmd = DL_RDHW32;

	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0),
		cmd, (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
		(uint16)(regaddr), (uint16)(regaddr >> 16),
		(void *) &hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);

	if (transferred >= sizeof(hwacc_t)) {
		*value = hwacc.data;
	} else {
		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
		ret = DBUS_ERR;
	}

	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

	return ret;
}

int
dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data)
{
	usbos_info_t *usbinfo = (usbos_info_t *) bus;
	int ret = DBUS_OK;
	int transferred;
	uint32 cmd = DL_WRHW;
	hwacc_t	hwacc;

	if (usbinfo == NULL)
		return DBUS_ERR;

	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

	hwacc.cmd = DL_WRHW;
	hwacc.addr = regaddr;
	hwacc.data = data;
	hwacc.len = datalen;

	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0),
		cmd, (USB_DIR_OUT| USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
		1, 0,
		(void *) &hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);

	if (transferred != sizeof(hwacc_t)) {
		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
		ret = DBUS_ERR;
	}

	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

	return ret;
}

int
dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
	if (in_interrupt())
		mdelay(ms);
	else
		msleep_interruptible(ms);
#else
	wait_ms(ms);
#endif
	return DBUS_OK;
}

/** Called by dbus_usb.c as part of the firmware download process */
bool
dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len)
{
	bool ret = TRUE;
	int status;
	int transferred = 0;

	if (usbinfo == NULL)
		return DBUS_ERR;

	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

	status = USB_BULK_MSG(usbinfo->usb, usbinfo->tx_pipe,
		buffer, len,
		&transferred, USB_BULK_EP_TIMEOUT);

	if (status < 0) {
		DBUSERR(("%s: usb_bulk_msg failed %d\n", __FUNCTION__, status));
		ret = FALSE;
	}

	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);

	return ret;
}

static bool
dbus_usbos_intf_recv_needed(void *bus)
{
	return FALSE;
}

/**
 * Higher layer (dbus_usb.c) wants to execute a function on the condition that the rx spin lock has
 * been acquired.
 */
static void*
dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb, struct exec_parms *args)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	void *ret;
	unsigned long flags;

	if (usbos_info == NULL)
		return NULL;

	spin_lock_irqsave(&usbos_info->rxlock, flags);
	ret = cb(args);
	spin_unlock_irqrestore(&usbos_info->rxlock, flags);

	return ret;
}

static void*
dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb, struct exec_parms *args)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;
	void *ret;
	unsigned long flags;

	if (usbos_info == NULL)
		return NULL;

	spin_lock_irqsave(&usbos_info->txlock, flags);
	ret = cb(args);
	spin_unlock_irqrestore(&usbos_info->txlock, flags);

	return ret;
}

/**
 * if an error condition was detected in this module, the higher DBUS layer (dbus_usb.c) has to
 * be notified.
 */
int
dbus_usbos_errhandler(void *bus, int err)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if (usbos_info == NULL)
		return DBUS_ERR;

	if (usbos_info->cbarg && usbos_info->cbs) {
		if (usbos_info->cbs->errhandler)
			usbos_info->cbs->errhandler(usbos_info->cbarg, err);
	}

	return DBUS_OK;
}

/**
 * if a change in bus state was detected in this module, the higher DBUS layer (dbus_usb.c) has to
 * be notified.
 */
int
dbus_usbos_state_change(void *bus, int state)
{
	usbos_info_t *usbos_info = (usbos_info_t *) bus;

	if (usbos_info == NULL)
		return DBUS_ERR;

	if (usbos_info->cbarg && usbos_info->cbs) {
		if (usbos_info->cbs->state_change)
			usbos_info->cbs->state_change(usbos_info->cbarg, state);
	}

	usbos_info->pub->busstate = state;
	return DBUS_OK;
}

int
dbus_bus_osl_register(int vid, int pid, probe_cb_t prcb,
	disconnect_cb_t discb, void *prarg, dbus_intf_t **intf, void *param1, void *param2)
{
	bzero(&g_probe_info, sizeof(probe_info_t));

	probe_cb = prcb;
	disconnect_cb = discb;
	probe_arg = prarg;

	devid_table[0].idVendor = vid;
	devid_table[0].idProduct = pid;

	*intf = &dbus_usbos_intf;

	USB_REGISTER();

	return DBUS_ERR_NODEVICE;
}

int
dbus_bus_osl_deregister()
{
	g_probe_info.dereged = TRUE;

	DHD_MUTEX_LOCK();
	if (disconnect_cb && disc_arg && (g_probe_info.disc_cb_done == FALSE)) {
		disconnect_cb(disc_arg);
		disc_arg = NULL;
	}
	DHD_MUTEX_UNLOCK();

	USB_DEREGISTER();

	return DBUS_OK;
}

void *
dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg, dbus_intf_callbacks_t *cbs)
{
	usbos_info_t *usbos_info;

	if (g_probe_info.dldone == FALSE) {
		DBUSERR(("%s: err device not downloaded!\n", __FUNCTION__));
		return NULL;
	}

	/* Sanity check for BUS_INFO() */
	ASSERT(OFFSETOF(usbos_info_t, pub) == 0);

	usbos_info = MALLOC(pub->osh, sizeof(usbos_info_t));
	if (usbos_info == NULL)
		return NULL;

	bzero(usbos_info, sizeof(usbos_info_t));

	usbos_info->pub = pub;
	usbos_info->cbarg = cbarg;
	usbos_info->cbs = cbs;

	/* Needed for disconnect() */
	g_probe_info.usbos_info = usbos_info;

	/* Update USB Info */
	usbos_info->usb = g_probe_info.usb;
	usbos_info->rx_pipe = g_probe_info.rx_pipe;
	usbos_info->rx_pipe2 = g_probe_info.rx_pipe2;
	usbos_info->tx_pipe = g_probe_info.tx_pipe;
	usbos_info->intr_pipe = g_probe_info.intr_pipe;
	usbos_info->intr_size = g_probe_info.intr_size;
	usbos_info->interval = g_probe_info.interval;
	usbos_info->pub->device_speed = g_probe_info.device_speed;
	if (usbos_info->rx_pipe2) {
		usbos_info->pub->attrib.has_2nd_bulk_in_ep = 1;
	} else {
		usbos_info->pub->attrib.has_2nd_bulk_in_ep = 0;
	}

	if (usbos_info->tx_pipe)
		usbos_info->maxps = usb_maxpacket(usbos_info->usb,
			usbos_info->tx_pipe, usb_pipeout(usbos_info->tx_pipe));

	INIT_LIST_HEAD(&usbos_info->req_rxfreeq);
	INIT_LIST_HEAD(&usbos_info->req_txfreeq);
	INIT_LIST_HEAD(&usbos_info->req_rxpostedq);
	INIT_LIST_HEAD(&usbos_info->req_txpostedq);
	spin_lock_init(&usbos_info->rxfree_lock);
	spin_lock_init(&usbos_info->txfree_lock);
	spin_lock_init(&usbos_info->rxposted_lock);
	spin_lock_init(&usbos_info->txposted_lock);
	spin_lock_init(&usbos_info->rxlock);
	spin_lock_init(&usbos_info->txlock);

	atomic_set(&usbos_info->rxposted, 0);
	atomic_set(&usbos_info->txposted, 0);


#ifdef USB_DISABLE_INT_EP
	usbos_info->intr_urb = NULL;
#else
	if (!(usbos_info->intr_urb = USB_ALLOC_URB())) {
		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
		goto fail;
	}
#endif

	if (!(usbos_info->ctl_urb = USB_ALLOC_URB())) {
		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
		goto fail;
	}

	init_waitqueue_head(&usbos_info->wait);

	if (!(usbos_info->blk_urb = USB_ALLOC_URB())) {	/* for embedded image downloading */
		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
		goto fail;
	}

	usbos_info->rxbuf_len = (uint)usbos_info->pub->rxsize;



	atomic_set(&usbos_info->txallocated, 0);
	if (DBUS_OK != dbus_usbos_urbreqs_alloc(usbos_info,
		usbos_info->pub->ntxq, FALSE)) {
		goto fail;
	}

	atomic_set(&usbos_info->rxallocated, 0);
	if (DBUS_OK != dbus_usbos_urbreqs_alloc(usbos_info,
		MIN(DBUS_USB_RXQUEUE_BATCH_ADD, usbos_info->pub->nrxq),
		TRUE)) {
		goto fail;
	}

	sema_init(&usbos_info->ctl_lock, 1);

#ifdef USBOS_THREAD
	if (dbus_usbos_thread_init(usbos_info) == NULL)
		goto fail;
#endif /* USBOS_THREAD */

#ifdef USBOS_TX_THREAD
	if (dbus_usbos_tx_thread_init(usbos_info) == NULL)
		goto fail;
#endif /* USBOS_TX_THREAD */

	pub->dev_info = g_probe_info.usb;


	return (void *) usbos_info;
fail:
	if (usbos_info->intr_urb) {
		USB_FREE_URB(usbos_info->intr_urb);
		usbos_info->intr_urb = NULL;
	}

	if (usbos_info->ctl_urb) {
		USB_FREE_URB(usbos_info->ctl_urb);
		usbos_info->ctl_urb = NULL;
	}

#if defined(BCM_REQUEST_FW)
	if (usbos_info->blk_urb) {
		USB_FREE_URB(usbos_info->blk_urb);
		usbos_info->blk_urb = NULL;
	}
#endif

	dbus_usbos_urbreqs_free(usbos_info, TRUE);
	atomic_set(&usbos_info->rxallocated, 0);
	dbus_usbos_urbreqs_free(usbos_info, FALSE);
	atomic_set(&usbos_info->txallocated, 0);

	g_probe_info.usbos_info = NULL;

	MFREE(pub->osh, usbos_info, sizeof(usbos_info_t));
	return NULL;
} /* dbus_usbos_intf_attach */

void
dbus_usbos_intf_detach(dbus_pub_t *pub, void *info)
{
	usbos_info_t *usbos_info = (usbos_info_t *) info;
	osl_t *osh = pub->osh;

	if (usbos_info == NULL) {
		return;
	}

#ifdef USBOS_TX_THREAD
	dbus_usbos_tx_thread_deinit(usbos_info);
#endif /* USBOS_TX_THREAD */

	/* Must unlink all URBs prior to driver unload;
	 * otherwise an URB callback can occur after driver
	 * has been de-allocated and rmmod'd
	 */
	dbusos_stop(usbos_info);

	if (usbos_info->intr_urb) {
		USB_FREE_URB(usbos_info->intr_urb);
		usbos_info->intr_urb = NULL;
	}

	if (usbos_info->ctl_urb) {
		USB_FREE_URB(usbos_info->ctl_urb);
		usbos_info->ctl_urb = NULL;
	}

	if (usbos_info->blk_urb) {
		USB_FREE_URB(usbos_info->blk_urb);
		usbos_info->blk_urb = NULL;
	}

	dbus_usbos_urbreqs_free(usbos_info, TRUE);
	atomic_set(&usbos_info->rxallocated, 0);
	dbus_usbos_urbreqs_free(usbos_info, FALSE);
	atomic_set(&usbos_info->txallocated, 0);

#ifdef USBOS_THREAD
	dbus_usbos_thread_deinit(usbos_info);
#endif /* USBOS_THREAD */

	g_probe_info.usbos_info = NULL;
	MFREE(osh, usbos_info, sizeof(usbos_info_t));
} /* dbus_usbos_intf_detach */


#ifdef USBOS_TX_THREAD

void*
dbus_usbos_tx_thread_init(usbos_info_t *usbos_info)
{
	spin_lock_init(&usbos_info->usbos_tx_list_lock);
	INIT_LIST_HEAD(&usbos_info->usbos_tx_list);
	init_waitqueue_head(&usbos_info->usbos_tx_queue_head);

	usbos_info->usbos_tx_kt = kthread_create(dbus_usbos_tx_thread_func,
		usbos_info, "usb-tx-thread");

	if (IS_ERR(usbos_info->usbos_tx_kt)) {
		DBUSERR(("Thread Creation failed\n"));
		return (NULL);
	}

	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
	wake_up_process(usbos_info->usbos_tx_kt);

	return (usbos_info->usbos_tx_kt);
}

void
dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info)
{
	urb_req_t *req;

	if (usbos_info->usbos_tx_kt) {
		wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
		kthread_stop(usbos_info->usbos_tx_kt);
	}

	/* Move pending requests to free queue so they can be freed */
	while ((req = dbus_usbos_qdeq(
		&usbos_info->usbos_tx_list, &usbos_info->usbos_tx_list_lock)) != NULL) {
		dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
	}
}

/**
 * Allow USB in-band resume to block by submitting CTRL and DATA URBs on a separate thread.
 */
int
dbus_usbos_tx_thread_func(void *data)
{
	usbos_info_t  *usbos_info = (usbos_info_t *)data;
	urb_req_t     *req;
	dbus_irb_tx_t *txirb;
	int           ret;
	unsigned long flags;

#ifdef WL_THREADNICE
	set_user_nice(current, WL_THREADNICE);
#endif

	while (1) {
		/* Wait until there are URBs to submit */
		wait_event_interruptible_timeout(
			usbos_info->usbos_tx_queue_head,
			!list_empty(&usbos_info->usbos_tx_list) ||
			usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED,
			100);

		if (kthread_should_stop())
			break;

		/* Submit CTRL URB if needed */
		if (usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED) {

			/* Disable USB autosuspend until this request completes. If the
			 * interface was suspended, this call blocks until it has been resumed.
			 */
			USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

			usbos_info->ctl_state = USBOS_REQUEST_STATE_SUBMITTED;

			ret = USB_SUBMIT_URB(usbos_info->ctl_urb);
			if (ret != 0) {
				DBUSERR(("%s CTRL USB_SUBMIT_URB failed, status %d\n",
					__FUNCTION__, ret));

				usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
				up(&usbos_info->ctl_lock);

				USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
			}
		}

		/* Submit all available TX URBs */
		while ((req = dbus_usbos_qdeq(&usbos_info->usbos_tx_list,
			&usbos_info->usbos_tx_list_lock)) != NULL) {

			/* Disable USB autosuspend until this request completes. If the
			 * interface was suspended, this call blocks until it has been resumed.
			 */
			USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);

			spin_lock_irqsave(&usbos_info->txlock, flags);

			ret = USB_SUBMIT_URB(req->urb);
			if (ret == 0) {
				/* URB submitted successfully */
				dbus_usbos_qenq(&usbos_info->req_txpostedq, req,
					&usbos_info->txposted_lock);
				atomic_inc(&usbos_info->txposted);
			} else {
				/* Submitting the URB failed. */
				DBUSERR(("%s TX USB_SUBMIT_URB failed, status %d\n",
					__FUNCTION__, ret));

				USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
			}

			spin_unlock_irqrestore(&usbos_info->txlock, flags);

			if (ret != 0) {
				/* Cleanup and notify higher layers */
				dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
					&usbos_info->txfree_lock);

				txirb = req->arg;
				if (txirb->send_buf) {
					MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
					txirb->send_buf = NULL;
					req->buf_len = 0;
				}

				if (likely (usbos_info->cbarg && usbos_info->cbs)) {
					if (likely (usbos_info->cbs->send_irb_complete != NULL))
						usbos_info->cbs->send_irb_complete(
							usbos_info->cbarg, txirb, DBUS_ERR_TXDROP);
				}
			}
		}
	}

	return 0;
} /* dbus_usbos_tx_thread_func */

#endif /* USBOS_TX_THREAD */

#ifdef USBOS_THREAD

/**
 * Increase system performance by creating a USB thread that runs parallel to other system
 * activity.
 */
static void*
dbus_usbos_thread_init(usbos_info_t *usbos_info)
{
	usbos_list_entry_t  *entry;
	unsigned long       flags, ii;

	spin_lock_init(&usbos_info->usbos_list_lock);
	spin_lock_init(&usbos_info->ctrl_lock);
	INIT_LIST_HEAD(&usbos_info->usbos_list);
	INIT_LIST_HEAD(&usbos_info->usbos_free_list);
	init_waitqueue_head(&usbos_info->usbos_queue_head);
	atomic_set(&usbos_info->usbos_list_cnt, 0);


	for (ii = 0; ii < (usbos_info->pub->nrxq + usbos_info->pub->ntxq); ii++) {
		entry = MALLOC(usbos_info->pub->osh, sizeof(usbos_list_entry_t));
		if (entry) {
			spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
			list_add_tail((struct list_head*) entry, &usbos_info->usbos_free_list);
			spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
		} else {
			DBUSERR(("Failed to create list\n"));
		}
	}

	usbos_info->usbos_kt = kthread_create(dbus_usbos_thread_func,
		usbos_info, "usb-thread");

	if (IS_ERR(usbos_info->usbos_kt)) {
		DBUSERR(("Thread Creation failed\n"));
		return (NULL);
	}

	wake_up_process(usbos_info->usbos_kt);

	return (usbos_info->usbos_kt);
}

static void
dbus_usbos_thread_deinit(usbos_info_t *usbos_info)
{
	struct list_head    *cur, *next;
	usbos_list_entry_t  *entry;
	unsigned long       flags;

	if (usbos_info->usbos_kt) {
		wake_up_interruptible(&usbos_info->usbos_queue_head);
		kthread_stop(usbos_info->usbos_kt);
	}
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
	list_for_each_safe(cur, next, &usbos_info->usbos_list)
	{
		entry = list_entry(cur, struct usbos_list_entry, list);
		/* detach this entry from the list and then free the entry */
		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
		list_del(cur);
		MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
	}

	list_for_each_safe(cur, next, &usbos_info->usbos_free_list)
	{
		entry = list_entry(cur, struct usbos_list_entry, list);
		/* detach this entry from the list and then free the entry */
		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
		list_del(cur);
		MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
	}
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}

/** Process completed URBs in a worker thread */
static int
dbus_usbos_thread_func(void *data)
{
	usbos_info_t        *usbos_info = (usbos_info_t *)data;
	usbos_list_entry_t  *entry;
	struct list_head    *cur, *next;
	unsigned long       flags;

#ifdef WL_THREADNICE
	set_user_nice(current, WL_THREADNICE);
#endif

	while (1) {
		/* If the list is empty, then go to sleep */
		wait_event_interruptible_timeout
		(usbos_info->usbos_queue_head,
			atomic_read(&usbos_info->usbos_list_cnt) > 0,
			100);

		if (kthread_should_stop())
			break;

		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);

		/* For each entry on the list, process it.  Remove the entry from
		* the list when done.
		*/
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
		list_for_each_safe(cur, next, &usbos_info->usbos_list)
		{
			urb_req_t           *req;
			int                 len;
			int                 stat;
			usbos_info_t        *usbos_info_local;

			entry = list_entry(cur, struct usbos_list_entry, list);
			if (entry == NULL)
				break;

			req = entry->urb_context;
			len = entry->urb_length;
			stat = entry->urb_status;
			usbos_info_local = req->usbinfo;

			/* detach this entry from the list and attach it to the free list */
			list_del_init(cur);
			spin_unlock_irqrestore(&usbos_info_local->usbos_list_lock, flags);

			dbus_usbos_recv_complete_handle(req, len, stat);

			spin_lock_irqsave(&usbos_info_local->usbos_list_lock, flags);

			list_add_tail(cur, &usbos_info_local->usbos_free_list);

			atomic_dec(&usbos_info_local->usbos_list_cnt);
		}

		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);

	}
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

	return 0;
} /* dbus_usbos_thread_func */

/** Called on Linux calling URB callback, see dbus_usbos_recv_complete() */
static void
dbus_usbos_dispatch_schedule(CALLBACK_ARGS)
{
	urb_req_t           *req = urb->context;
	usbos_info_t        *usbos_info = req->usbinfo;
	usbos_list_entry_t  *entry;
	unsigned long       flags;
	struct list_head    *cur;

	spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);

	cur   = usbos_info->usbos_free_list.next;
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
#endif
	entry = list_entry(cur, struct usbos_list_entry, list);
#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

	/* detach this entry from the free list and prepare it insert it to use list */
	list_del_init(cur);

	if (entry) {
		entry->urb_context = urb->context;
		entry->urb_length  = urb->actual_length;
		entry->urb_status  = urb->status;

		atomic_inc(&usbos_info->usbos_list_cnt);
		list_add_tail(cur, &usbos_info->usbos_list);
	} else {
		DBUSERR(("!!!!!!OUT OF MEMORY!!!!!!!\n"));
	}

	spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);

	/* thread */
	wake_up_interruptible(&usbos_info->usbos_queue_head);
} /* dbus_usbos_dispatch_schedule */

#endif /* USBOS_THREAD */




#ifdef BCM_REQUEST_FW

struct request_fw_context {
	const struct firmware *firmware;
	struct semaphore lock;
};

/*
 * Callback for dbus_request_firmware().
 */
static void
dbus_request_firmware_done(const struct firmware *firmware, void *ctx)
{
	struct request_fw_context *context = (struct request_fw_context*)ctx;

	/* Store the received firmware handle in the context and wake requester */
	context->firmware = firmware;
	up(&context->lock);
}

/*
 * Send a firmware request and wait for completion.
 *
 * The use of the asynchronous version of request_firmware() is needed to avoid
 * kernel oopses when we just come out of system hibernate.
 */
static int
dbus_request_firmware(const char *name, const struct firmware **firmware)
{
	struct request_fw_context *context;
	int ret;

	context = kzalloc(sizeof(*context), GFP_KERNEL);
	if (!context)
		return -ENOMEM;

	sema_init(&context->lock, 0);

	ret = request_firmware_nowait(THIS_MODULE, true, name, &g_probe_info.usb->dev,
	                              GFP_KERNEL, context, dbus_request_firmware_done);
	if (ret) {
		kfree(context);
		return ret;
	}

	/* Wait for completion */
	if (down_interruptible(&context->lock) != 0) {
		kfree(context);
		return -ERESTARTSYS;
	}

	*firmware = context->firmware;
	kfree(context);

	return *firmware != NULL ? 0 : -ENOENT;
}

static void *
dbus_get_fwfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev)
{
	const struct firmware *firmware = NULL;
#ifndef OEM_ANDROID
	s8 *device_id = NULL;
	s8 *chip_rev = "";
#endif /* OEM_ANDROID */
	s8 file_name[64];
	int ret;

#ifndef OEM_ANDROID
	switch (devid) {
		case BCM4350_CHIP_ID:
		case BCM4354_CHIP_ID:
		case BCM43556_CHIP_ID:
		case BCM43558_CHIP_ID:
		case BCM43566_CHIP_ID:
		case BCM43568_CHIP_ID:
		case BCM43570_CHIP_ID:
		case BCM4358_CHIP_ID:
			device_id = "4350";
			break;
		case BCM43143_CHIP_ID:
			device_id = "43143";
			break;
		case BCM43234_CHIP_ID:
		case BCM43235_CHIP_ID:
		case BCM43236_CHIP_ID:
			device_id = "43236";
			break;
		case BCM43242_CHIP_ID:
			device_id = "43242";
			break;
		case BCM43238_CHIP_ID:
			device_id = "43238";
			break;
		case BCM43526_CHIP_ID:
			device_id = "43526";
			break;
		case BCM43569_CHIP_ID:
			device_id = "43569";
			switch (chiprev) {
				case 0:
					chip_rev = "a0";
					break;
				case 2:
					chip_rev = "a2";
					break;
				default:
					break;
			}
			break;
		default:
			DBUSERR(("unsupported device %x\n", devid));
			return NULL;
	}

	/* Load firmware */
	snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-firmware.bin", device_id, chip_rev);
#else
	snprintf(file_name, sizeof(file_name), "%s", CONFIG_ANDROID_BCMDHD_FW_PATH);
#endif /* OEM_ANDROID */

	ret = dbus_request_firmware(file_name, &firmware);
	if (ret) {
		DBUSERR(("fail to request firmware %s\n", file_name));
		return NULL;
	}

	*fwlen = firmware->size;
	*fw = (uint8 *)firmware->data;
	return (void *)firmware;

}

static void *
dbus_get_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev)
{
	const struct firmware *firmware = NULL;
#ifndef OEM_ANDROID
	s8 *device_id = NULL;
	s8 *chip_rev = "";
#endif /* OEM_ANDROID */
	s8 file_name[64];
	int ret;

#ifndef OEM_ANDROID
	switch (devid) {
		case BCM4350_CHIP_ID:
		case BCM4354_CHIP_ID:
		case BCM43556_CHIP_ID:
		case BCM43558_CHIP_ID:
		case BCM43566_CHIP_ID:
		case BCM43568_CHIP_ID:
		case BCM43570_CHIP_ID:
		case BCM4358_CHIP_ID:
			device_id = "4350";
			break;
		case BCM43143_CHIP_ID:
			device_id = "43143";
			break;
		case BCM43234_CHIP_ID:
			device_id = "43234";
			break;
		case BCM43235_CHIP_ID:
			device_id = "43235";
			break;
		case BCM43236_CHIP_ID:
			device_id = "43236";
			break;
		case BCM43238_CHIP_ID:
			device_id = "43238";
			break;
		case BCM43242_CHIP_ID:
			device_id = "43242";
			break;
		case BCM43526_CHIP_ID:
			device_id = "43526";
			break;
		case BCM43569_CHIP_ID:
			device_id = "43569";
			switch (chiprev) {
				case 0:
					chip_rev = "a0";
					break;
				case 2:
					chip_rev = "a2";
					break;
				default:
					break;
			}
			break;
		default:
			DBUSERR(("unsupported device %x\n", devid));
			return NULL;
	}

	/* Load board specific nvram file */
	snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-%2x-%2x.nvm",
	         device_id, chip_rev, boardtype, boardrev);
#else
	snprintf(file_name, sizeof(file_name), "%s", CONFIG_ANDROID_BCMDHD_NVRAM_PATH);
#endif /* OEM_ANDROID */

	ret = dbus_request_firmware(file_name, &firmware);
	if (ret) {
		DBUSERR(("fail to request nvram %s\n", file_name));

#ifndef OEM_ANDROID
		/* Load generic nvram file */
		snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s.nvm",
		         device_id, chip_rev);

		ret = dbus_request_firmware(file_name, &firmware);
#endif /* OEM_ANDROID */

		if (ret) {
			DBUSERR(("fail to request nvram %s\n", file_name));
			return NULL;
		}
	}

	*fwlen = firmware->size;
	*fw = (uint8 *)firmware->data;
	return (void *)firmware;
}

void *
dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, int type, uint16 boardtype,
	uint16 boardrev)
{
	switch (type) {
		case DBUS_FIRMWARE:
			return dbus_get_fwfile(devid, chiprev, fw, fwlen, boardtype, boardrev);
		case DBUS_NVFILE:
			return dbus_get_nvfile(devid, chiprev, fw, fwlen, boardtype, boardrev);
		default:
			return NULL;
	}
}

void
dbus_release_fw_nvfile(void *firmware)
{
	release_firmware((struct firmware *)firmware);
}
#endif /* BCM_REQUEST_FW */

#ifdef BCMUSBDEV_COMPOSITE
/**
 * For a composite device the interface order is not guaranteed, scan the device struct for the WLAN
 * interface.
 */
static int
dbus_usbos_intf_wlan(struct usb_device *usb)
{
	int i, num_of_eps, ep, intf_wlan = -1;
	int num_intf = CONFIGDESC(usb)->bNumInterfaces;
	struct usb_endpoint_descriptor *endpoint;

	for (i = 0; i < num_intf; i++) {
		if (IFDESC(usb, i).bInterfaceClass != USB_CLASS_VENDOR_SPEC)
			continue;
		num_of_eps = IFDESC(usb, i).bNumEndpoints;

		for (ep = 0; ep < num_of_eps; ep++) {
			endpoint = &IFEPDESC(usb, i, ep);
			if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
				USB_ENDPOINT_XFER_BULK) {
				intf_wlan = i;
				break;
			}
		}
		if (ep < num_of_eps)
			break;
	}

	return intf_wlan;
}
#endif /* BCMUSBDEV_COMPOSITE */
